两子串的公共子序列
两子串的公共子序列,子序列的问题难比子串,暴力也难搞,动态规划大法好,
1.确定dp[i][j]:dp[i][j]表示字符串str1的[0,i]和str2的[0,j]的最大公共子序列
2.填已经确定的dp值,这里是第一行str1的[0,n1]和str2的[0]的最大公共子序列,第一列str1的[0]和str2的[0,n2]的最大公共子序列,
3.找递归关系,根据已知求未知,dp[i][j]可能来自三个方向,左上方dp[i-1][j-1],上方dp[i-1][j],左方dp[i-1][j],选择三者最大值,想不明白的话真的难啊
4.dp[n1][n2]的返回的是str1和str2这道题要返回公共子序列,需要根据dp表倒推从dp[0][0]到dp[n1][n2]的路径
// 时间复杂度O(M*N),空间复杂度
public class ComSubSequence {
public int[][] getDp(String str1, String str2) {
int m = str1.length(), n = str2.length();
char[] s1 = str1.toCharArray(), s2 = str2.toCharArray();
int[][] dp = new int[m][n];
dp[0][0] = s1[0] == s2[0] ? 1 : 0;// 如果两个字符串的首字母相等,则
for (int i = 1; i != m; i++) {// 列表示str1的变化范围,只跟str2[0]比较,第一列最大可能值是1
dp[i][0] = dp[i - 1][0] == 1 || s1[i] == s2[0] ? 1 : 0;
}
for (int i = 1; i != n; i++) {// 行表示str2的变化范围,只跟str1[0]比较,第一行最大可能值是1,
dp[0][i] = dp[0][i - 1] == 1 || s2[i] == s1[0] ? 1 : 0;
}
for (int i = 1; i != m; i++) {// 从dp[1][1]位置开始递推
for (int j = 1; j != n; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
dp[i][j] = s1[i] == s2[j] ? dp[i - 1][j - 1] + 1 : dp[i][j];
}
}
return dp;
}
public String lcse(String str1, String str2) {
// 根据dp表找路径,与构建dp时相反,从左往右,从上往下来到dp[i][j]的方向有3个,从左过来的,从上过来的,从左上角过来
int m = str1.length() - 1, n = str2.length() - 1;
int[][] dp = getDp(str1, str2);
char[] res = new char[dp[m][n]];//最大公共串长度为dp[m][n],申请辅助数组,存公共串
int index = res.length - 1;
while (index >= 0) {// 这个根据长度找路径还是挺陌生的
if (m > 0 && dp[m][n] == dp[m - 1][n]) {// 从上方来的,str1[0,m]和str2[0,n]的最长子串等于str1[0,m-1]
m--;
} else if (n > 0 && dp[m][n] == dp[m][n - 1]) {// 从左方来的,str1[0,m]和str2[0,n]的最长子串等于str1[0,m-1]
n--;
} else {// 当前位置是两者的公共部分
res[index--] = str1.charAt(m);// ==str2.charAt(n)
m--;
n--;
}
}
return String.valueOf(res);
}
public static void main(String[] args) {
String str1 = "A1BC2D3EFGH45I6JK7L8M9N", str2 = "12OPQ3RST4U5V6W7XYZ89";
ComSubSequence cs = new ComSubSequence();
String res = cs.lcse(str1, str2);
System.out.println(res);
}
}