[LCS]最长公共子串和最长公共子序列
1.Longest Common Substring【最长公共子串】
最重要的一点是抓住DP[i][j]的含义,以a[i]和b[j]作为结尾元素的公共子串的长度。注意是必须以他俩作为结尾。
如果当前a[i]和b[j]都不相等,以他们结尾那肯定是0,如果当前a[i]和b[j]相等,那么就在原来dp[i-1][j-1]的基础上加上这一位即可。
JAVA版本参考代码:
//max_common为最长的公共串长度
//end是以str1作为基准,str1中的第几位
public static String LCS(String str1, String str2) {
if (str1 == null || str2 == null) {
return "";
}
int[][] dp = new int[str1.length() + 1][str2.length() + 1];
int max_common = 0;
int end = 0;
for (int i = 1; i <= str1.length(); i++) {
for (int j = 1; j <= str2.length(); j++) {
if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
if (dp[i][j] > max_common) {
max_common = dp[i][j];
end = i;
}
} else {
dp[i][j] = 0;
}
}
}
return str1.substring(end - max_common, end);
}
2.Longest Common sequence 【最长公共子序列】
和1不同,字符可离散分布于字符串中。
DP[i][j]表示a的前i个字符和b的前j个字符的最长公共序列(联想背包问题DP含义),我们最终想得到的就是DP[a.length][b.length]。
如果a[i]和a[j]相等,那么DP[i][j]就相当于在DP[i-1][j-1]基础上增加一。
如果不等,我们需要将DP[i][j]分解为子问题,(1)假设舍弃a[i],子问题为a的前i-1个字符和b的前j个字符的最长序列,即DP[i-1][j]。(2)假设舍弃b[j],子问题为a的前i个字符和b的前j-1个字符的最长序列。在(1)(2)选择较好的一种,即DP[i][j] = max(DP[i-1][j],DP[i][j-1])
如果上面的叙述转换到打表,即相等考虑左上角加一,不等取左/上最大值。
JAVA版本参考代码:
public static int LCS(String str1, String str2) {
if (str2 == null || str1 == null) {
return 0;
}
int[][] dp = new int[str1.length() + 1][str2.length() + 1];
for (int i = 1; i <= str1.length(); i++) {
for (int j = 1; j <= str2.length(); j++) {
if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[str1.length()][str2.length()];
}
如果想得到这个序列是什么,从dp[a.length][b.length]往前回溯即可。
参考代码:
public void dfs(String str1, String str2, int x, int y, StringBuilder sb) {
if (x == 0 || y == 0) {
ans.add(sb.reverse().toString());
return;
}
if (str1.charAt(x - 1) == str2.charAt(y - 1)) {
sb.append(str1.charAt(x - 1));
dfs(str1, str2, x - 1, y - 1, sb);
sb.deleteCharAt(sb.length() - 1);
} else {
if (dp[x - 1][y] == dp[x][y]) {//从上面来的
dfs(str1, str2, x - 1, y, sb);
}
if (dp[x][y - 1] == dp[x][y]) {//如果是从左边来的
dfs(str1, str2, x, y - 1, sb);
}
}
}
合并:
public class Solution2 {
int[][] dp;
List<String> ans = new ArrayList<>();
public void LCS(String str1, String str2) {
if (str2 == null || str1 == null) {
return;
}
int[][] dp = new int[str1.length() + 1][str2.length() + 1];
for (int i = 1; i <= str1.length(); i++) {
for (int j = 1; j <= str2.length(); j++) {
if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
int res = dp[str1.length()][str2.length()];
System.out.println("最长子序列长度为\t" + res);
this.dp = dp;
dfs(str1, str2, str1.length(), str2.length(), new StringBuilder());
System.out.println("最长子序列为\t");
for (String an : ans) {
System.out.println(an);
}
}
public void dfs(String str1, String str2, int x, int y, StringBuilder sb) {
if (x == 0 || y == 0) {
ans.add(sb.reverse().toString());
return;
}
if (str1.charAt(x - 1) == str2.charAt(y - 1)) {
sb.append(str1.charAt(x - 1));
dfs(str1, str2, x - 1, y - 1, sb);
sb.deleteCharAt(sb.length() - 1);
} else {
if (dp[x - 1][y] == dp[x][y]) {//从上面来的
dfs(str1, str2, x - 1, y, sb);
}
if (dp[x][y - 1] == dp[x][y]) {//如果是从左边来的
dfs(str1, str2, x, y - 1, sb);
}
}
}
}