目录
https://leetcode.com/problems/longest-common-subsequence/
题目(从暴力递归到动态规划)
给定两个数组str1和str2,返回它们的公共最长子序列长度。
例:str1 = "as123d24"
str2 = "dsc12t32h4"
最长公共子序列为:s1234d,长度为6.
暴力递归
思路:遍历到每一个字符可能出现的每一种情况,我们可以对str1[0....i]和str2[0....j]逐一比较;
有四种情况:
-
i==0且j==0;若此时str1==str2,返回1,否则0;
-
i==0但j!=0;若此时str1==str2,返回1,否则递归 j-1;
-
i!=0但j==0;若此时str1==str2,返回1,否则递归 i-1;
-
i!=0且j!=0;这里又可以细分为三种情况:i减小;j减小;i和j同时减小。
代码如下
//暴力递归
public static int longestCommonSubsequence (String str1, String str2) {
if (str1==null || str2==null || str1.length()==0 || str2.length()==0) {
return 0;
}
return process(str1.toCharArray(),str2.toCharArray(),str1.length()-1,str2.length()-1);
}
public static int process (char[] str1, char[] str2, int i, int j) {
if (i == 0 && j ==0) {
return str1[i] == str2[j] ? 1 : 0;
} else if (i == 0) {
if (str1[i] == str2[j]) {
return 1;
} else {
return process(str1,str2,i,j-1);
}
} else if (j == 0) {
if (str1[i] == str2[j]) {
return 1;
} else {
return process(str1,str2,i-1,j);
}
} else { //i!=0且j!=0;
int p1 = process(str1,str2,i-1,j);
int p2 = process(str1,str2,i,j-1);
int p3 = str1[i] == str2[j] ? 1+process(str1,str2,i-1,j-1) : 0;
return Math.max(p1,Math.max(p2,p3));
}
}
测试
public static void main(String[] args) {
System.out.println(longestCommonSubsequence("as123d24","dsc12t32h4"));
}
输出
6
动态规划
在递归的过程中,i和j是全过程中不断变化的量,我们可以用一个二维数组将每一个i/j对应的值记录下来。
i的取值范围:0~str1.length()-1;
j的取值范围:0~str2.length()-1;
对二维数组的赋值方法类比上述暴力递归过程:
public static int longestCommonSubsequence1 (String str1, String str2) {
if (str1==null || str2==null || str1.length()==0 || str2.length()==0) {
return 0;
}
int N = str1.length();
int M = str2.length();
int[][] dp = new int[N][M];
char[] s1 = str1.toCharArray();
char[] s2 = str2.toCharArray();
//i==0且j==0;
dp[0][0] = s1[0] == s2[0] ? 1 : 0;
//i==0且j!=0;
for (int j = 1; j < M; j++) {
dp[0][j] = s1[0] == s2[j] ? 1 : process(s1,s2,0,j-1);
}
//j==0且i!=0;
for (int i = 1; i < N; i++) {
dp[i][0] = s1[i] == s2[0] ? 1 : process(s1,s2,i-1,0);
}
//j!=0且i!=0;
for (int i = 1; i < N; i++) {
for (int j = 1; j < M; j++) {
int p1 = dp[i-1][j];
int p2 = dp[i][j-1];
int p3 = s1[i] == s2[j] ? 1 + dp[i-1][j-1] : 0;
dp[i][j] = Math.max(p1,Math.max(p2,p3));
}
}
return dp[N-1][M-1];
}
测试
public static void main(String[] args) {
System.out.println(longestCommonSubsequence("as123d24","dsc12t32h4"));
System.out.println(longestCommonSubsequence1("as123d24","dsc12t132h4"))
}
输出
6
6
最长回文子序列
最长回文子序列的解法有多种,这里拿出来是因为可以用上述最长公共子序列问题求解。
假定这里给定一个字符串:"a122egdfg3h42q1"
我们只需要算出这个字符串的逆字符串:“1q24h3gfdge221a”
然后对两个字符串求解公共最长子序列,即为最长回文子序列长度。
这里给一个字符串逆串求解思路
public static int longestMirrorSubsequence(String str) {
if (str == null || str.length()==0) {
return 0;
}
String mirror = "";
char[] c = str.toCharArray();
for (int i = c.length-1; i >=0; i--) {
mirror += c[i];
}
return longestCommonSubsequence1(str,mirror);
}
测试
public static void main(String[] args) {
System.out.println(longestCommonSubsequence("as123d24","dsc12t32h4"));
System.out.println(longestCommonSubsequence1("as123d24","dsc12t132h4"));
System.out.println(longestMirrorSubsequence("a122egdfg3h42q1"));
}
输出
6
6
7