子数组:
连续子串:
连续子序列:
不连续
动态规划算法:与分治法类似,其基本思想也是将
待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。
1. 最长无重复子数组 - 滑动窗口
子数组:连续!
- 滑动窗口,设置左右两个指针,初始值都为0
- 用set来存储目前滑窗内的数据
- 当元素不重复时,则将不重复元素添加到set,右指针+1
- 当元素重复时,则进行结算,统计此窗口的长度,r-l+1
- 左指针+1,移除左指针的前一个元素,保证窗口是从左指针开始
- 最终在所有结算的最长长度中再选出最长的
import java.util.*;
public class Solution {
/**
*
* @param arr int整型一维数组 the array
* @return int整型
*/
public int maxLength (int[] arr) {
int r = 0;
int res = 0;
Set<Integer> mySet = new HashSet<Integer>();
for(int l=0;l<arr.length;l++){
if(l !=0 ){
mySet.remove(arr[l-1]);
}
while(r<arr.length && !mySet.contains(arr[r])){
mySet.add(arr[r]);
r++;
}
res = Math.max(res,r-l);
}
return res;
}
}
2. 最长公共子串 - 动态规划
- dp[i+1][j+1]用于记录以字符str1.charAt(i)和str2.charAt(j)
结尾的最长子串的长度。dp数组的第一行和第一列使用默认值0。 - dp只是记载长度,因此两个中间变量用于记录,
最长长度及最长长度时str1的结束索引,以助于最终获得最长子串的具体字符串。 如果两个结尾的字符相等,则dp[i+1][j+1] = dp[i][j]+1;如果两个结尾的字符不相等,则dp[i+1][j+1] = 0;
import java.util.*;
public class Solution {
/**
* longest common substring
* @param str1 string字符串 the string
* @param str2 string字符串 the string
* @return string字符串
*/
public String LCS (String str1, String str2) {
// dp[i][j]用于记录以字符str1.charAt(i-1)和str2.charAt(j-1)结尾的最长子串的长度
int[][] dp = new int[str1.length()+1][str2.length()+1];
//dp只是记载长度,因此两个中间变量用于记录,最长长度 及 对应的索引
int maxLength = 0;
int lastIndex = 0;
//dp[0][j]和dp[i][0] 都使用默认值0
for(int i=0;i<str1.length();i++){
for(int j=0;j<str2.length();j++){
//如果两字符相等,则dp[i+1][j+1] = dp[i][j]+1;
if(str1.charAt(i) == str2.charAt(j)){
dp[i+1][j+1] = dp[i][j]+1;
if(dp[i+1][j+1]>maxLength){
maxLength = dp[i+1][j+1];
lastIndex = i;
}
}else{
//如果两字符不相等则dp[i+1][j+1] = 0
dp[i+1][j+1] = 0;
}
}
}
return str1.substring(lastIndex-maxLength+1,lastIndex+1);
}
}
3. 最长回文子串 - 动态规划
- boolean dp[l][r] 记录索引l到索引r的字符串是否为回文字符串。
- dp对角线都为true。
- 2-3个字符时,只要第一个和最后一个字符相同,也是true (因为dp[l+1][r-1]需要r至少比l大3个,才不至于迭代的时候l>=r)
- 如果l的字符和r的字符相等,且dp[l+1][r-1]==true 则dp[l][r]=true。否则为false。
import java.util.*;
public class Solution {
public int getLongestPalindrome(String A, int n) {
// write code here
char[] arr = A.toCharArray();
boolean[][] dp = new boolean[n][n];
//初始化:对角线都为true
for(int i=0;i<n;i++){
dp[i][i] = true;
}
int max = 1;
for(int r=1;r<n;r++){
for(int l=0;l<r;l++){
//2-3个字符时,只要第一个和最后一个字符相同,也是true
if(r - l <= 2){
dp[l][r] = arr[l] == arr[r];
}else{
if(arr[l] == arr[r] && dp[l+1][r-1]){
dp[l][r] = true;
}else{
dp[l][r] = false;
}
}
if(dp[l][r]){
max = Math.max(max,r-l+1);
}
}
}
return max;
}
}
4. 最长公共子序列
4.1 最长公共子序列①
4.2 最长公共子序列② -动态规划
- dp[i+1][j+1]表示以s1.charAt(i)和s2.charAt(j)结尾的字符串的最长公共子序列的长度。
- dp第一行和第一列使用0作为默认值。
- 如果s1.charAt(i)和s2.charAt(j)相等,则dp[i+1][j+1] = dp[i][j]+1;否则dp[i+1][j+1] = Math.max(dp[i][j+1],dp[i+1][j])。
- 根据最长公共子序列的长度,反过来求得子序列实际值。
- 从尾部开始遍历,如果尾部字符相等,则两个尾部指针同时减1,否则比较dp上面和左边的值,哪边值更大,就往哪边移动。
import java.util.*;
public class Solution {
/**
* longest common subsequence
* @param s1 string字符串 the string
* @param s2 string字符串 the string
* @return string字符串
*/
public String LCS (String s1, String s2) {
// dp[i+1][j+1]表示以s1.charAt(i)和s2.charAt(j)结尾的字符串的最长公共子序列的长度
int[][] dp = new int[s1.length()+1][s2.length()+1];
for(int i=0;i<s1.length();i++){
for(int j=0;j<s2.length();j++){
if(s1.charAt(i) == s2.charAt(j)){
dp[i+1][j+1] = dp[i][j]+1;
}else{
dp[i+1][j+1] = Math.max(dp[i][j+1],dp[i+1][j]);
}
}
}
//根据最长公共子序列的长度,反过来求得子序列实际值
StringBuffer sb = new StringBuffer();
int l1 = s1.length()-1;
int l2 = s2.length()-1;
//从尾部开始遍历,如果尾部字符相等,则两个尾部指针同时减1
//否则比较dp上面和左边的值,哪边值更大,就往哪边移动
while(l1>=0&&l2>=0){
if(s1.charAt(l1) == s2.charAt(l2)){
sb.append(s1.charAt(l1));
l1--;
l2--;
}else{
//因为dp[i+1][j+1]代表的是(i,j),所以比较的时候要+1
if(dp[l1+1][l2] > dp[l1][l2+1]){
l2--;
}else{
l1--;
}
}
}
if(sb.length() == 0){
return "-1";
}
return sb.reverse().toString();
}
}
5. 最长递增子序列
- 我们将大问题拆分为小问题,把数组缩短,再慢慢加长直到完整。如示例 1 中 原数组 arr = [2, 1, 5, 3, 6, 4, 8, 9, 7] 我们把最初的子问题定为 [2, 1],下一个子问题既往后加长一位 [2, 1, 5],以此类推。
- 为了能在遍历完子问题后精确地在原数组 arr 中找出组成最长递增子序列 LCS 的元素,我们可以使用“标号”的方法,在 arr 中组成 LCS 的元素上标上序号,比如示例 1 中 arr = [2, 1, 5, 3, 6, 4, 8, 9, 7], LCS = [1, 3, 4, 8, 9],LCS[0] = arr[1],LCS[1] = arr[3],LCS[2] = arr[5]。所以如何标号就是这个问题的关键。
- 如何标号呢?我们需要新增一个数组 temp,每当子问题增加一个元素 e 时,e 就与 temp 最后一个元素就进行比较,如果 e 比 temp 的最后一个元素大,则直接在 temp 最后面添加 e;反之,则在 temp 中从左往右寻找第一个比 e 大的数,并用 e 替换之。然后 e 在 temp 中的索引就是我们要找的标号,我们将标号存起来,继续下一个子问题。
- 在 nums 中标完号后,为了满足题目要求的字典序最小,我们需要从后往前遍历,标号从大到小,倒着填入 LCS 中,最后我们获得结果 LCS。
import java.util.*;
public class Solution {
/**
* retrun the longest increasing subsequence
* @param arr int整型一维数组 the array
* @return int整型一维数组
*/
public int[] LIS (int[] arr) {
int n = arr.length;
//temp用于存放子序列
int[] temp = new int[n];
//lens[i]用于存放以arr[i]结尾时,最长递增子序列的长度
int[] lens = new int[n];
if(n == 0){
return new int[0];
}
//初始化
int tempIndex = 0;
temp[tempIndex] = arr[0];
lens[0] = 1;
for(int i=1;i<n;i++){
if(arr[i]>temp[tempIndex]){
tempIndex++;
lens[i] = tempIndex+1;
temp[tempIndex] = arr[i];
}else{
//替换temp中第一个比arr[i]大于等于的值
int left = 0;
int right = tempIndex;
while(left <= right){
// 注意这里 left <= right 而不是 left < right,我们要替换的是第一个比 arr[i] 大的元素
int mid = (left+right)/2;
if(temp[mid] >= arr[i]){
right = mid -1;
}else{
left = mid +1;
}
}
temp[left] = arr[i];
lens[i] = left+1;
}
}
int[] res = new int[tempIndex+1];
for(int i = n-1;i>=0;i--){
if(lens[i] == tempIndex+1){
res[tempIndex] = arr[i];
tempIndex--;
}
}
return res;
}
}
6. 最长连续子序列
- 进行数组排序。
- 如果arr[i]-arr[i-1]==1 ,则计数+1。
- 如果arr[i] == arr[i-1],则跳过不进行计数。
- 如果arr[i]-arr[i-1] > 1,则重新开始计数。
- 从所有的计数中选出最大的数。
import java.util.*;
public class Solution {
/**
* max increasing subsequence
* @param arr int整型一维数组 the array
* @return int整型
*/
public int MLS (int[] arr) {
// write code here
Arrays.sort(arr);
if(arr.length == 0){
return 0;
}
int res = 1;
int temp = 1;
for(int i=1;i<arr.length;i++){
if(arr[i]-arr[i-1]==1){
temp++;
}else if(arr[i] == arr[i-1]){
continue;
}else{
res = Math.max(res,temp);
temp = 1;
}
}
return Math.max(res,temp);
}
}
7. 最长公共前缀
- 循环数组中字符串,依次比较第一个字符串中的字符是否在每个字符串的对应位置出现。
- 如果都有出现,则在最终结果中append该字符。
- 否则终止循环,返回。
import java.util.*;
public class Solution {
/**
*
* @param strs string字符串一维数组
* @return string字符串
*/
public String longestCommonPrefix (String[] strs) {
// write code here
if(strs.length == 0){
return "";
}
StringBuffer sb = new StringBuffer();
for(int i=0;i<strs[0].length();i++){
char x = strs[0].charAt(i);
for(int j=1;j<strs.length;j++){
if(strs[j].length() > i){
if(strs[j].charAt(i) != x){
return sb.toString();
}
}else{
return sb.toString();
}
}
sb.append(x);
}
return sb.toString();
}
}