[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);
            }
        }
    }
}

posted @ 2023-12-03 13:01  YuKiCheng  阅读(64)  评论(0编辑  收藏  举报  来源