基本概念

科普下,最长公共子序列(🍗最长公共子序列、最长公共子串合集 - 图1)和最长公共子串(🍗最长公共子序列、最长公共子串合集 - 图2)不是一回事儿。

什么是子序列、子串呢?
🍗最长公共子序列、最长公共子串合集 - 图3
最长公共子序列(以下都简称 LCS

解题子序列问题:动态规划

求解LCS问题,不能使用暴力搜索方法。一个长度为 🍗最长公共子序列、最长公共子串合集 - 图4 的序列拥有 🍗最长公共子序列、最长公共子串合集 - 图5 次方个子序列,它的时间复杂度是指数阶,太恐怖了。解决LCS问题,需要借助动态规划的思想。

假设字符串 🍗最长公共子序列、最长公共子串合集 - 图6🍗最长公共子序列、最长公共子串合集 - 图7的长度分别为 🍗最长公共子序列、最长公共子串合集 - 图8🍗最长公共子序列、最长公共子串合集 - 图9 ,创建 🍗最长公共子序列、最长公共子串合集 - 图10🍗最长公共子序列、最长公共子串合集 - 图11 列的二维数组 🍗最长公共子序列、最长公共子串合集 - 图12

👉 定义 🍗最长公共子序列、最长公共子串合集 - 图13 表示为 🍗最长公共子序列、最长公共子串合集 - 图14🍗最长公共子序列、最长公共子串合集 - 图15 的最长公共子序列的长度。

上述表示中 text1[0:i] 表示 text1 的长度为 i 的前缀,text2[0:j] 表示 text2 的长度为 j 的前缀。

考虑动态规划的边界情况:

  1. 🍗最长公共子序列、最长公共子串合集 - 图16 时,🍗最长公共子序列、最长公共子串合集 - 图17 为空,空字符串和任何字符串的最长公共子序列的长度都是 🍗最长公共子序列、最长公共子串合集 - 图18,因此对任意 🍗最长公共子序列、最长公共子串合集 - 图19,有 🍗最长公共子序列、最长公共子串合集 - 图20
  2. 🍗最长公共子序列、最长公共子串合集 - 图21 时,🍗最长公共子序列、最长公共子串合集 - 图22 为空,空字符串和任何字符串的最长公共子序列的长度都是🍗最长公共子序列、最长公共子串合集 - 图23,因此对任意 🍗最长公共子序列、最长公共子串合集 - 图24 ,有 🍗最长公共子序列、最长公共子串合集 - 图25

因此动态规划的边界情况是:当 🍗最长公共子序列、最长公共子串合集 - 图26🍗最长公共子序列、最长公共子串合集 - 图27 时,🍗最长公共子序列、最长公共子串合集 - 图28
🍗最长公共子序列、最长公共子串合集 - 图29🍗最长公共子序列、最长公共子串合集 - 图30 时,考虑 🍗最长公共子序列、最长公共子串合集 - 图31 的计算:

  1. 🍗最长公共子序列、最长公共子串合集 - 图32 时, 🍗最长公共子序列、最长公共子串合集 - 图33
  • 考虑 🍗最长公共子序列、最长公共子串合集 - 图34🍗最长公共子序列、最长公共子串合集 - 图35的最长公共子序列,再增加一个字符(即公共字符)即可得到 🍗最长公共子序列、最长公共子串合集 - 图36🍗最长公共子序列、最长公共子串合集 - 图37的最长公共子序列
  1. 🍗最长公共子序列、最长公共子串合集 - 图38 时,考虑以下两项:
  • 🍗最长公共子序列、最长公共子串合集 - 图39🍗最长公共子序列、最长公共子串合集 - 图40 的最长公共子序列
  • 🍗最长公共子序列、最长公共子串合集 - 图41🍗最长公共子序列、最长公共子串合集 - 图42 的最长公共子序列。

🍗最长公共子序列、最长公共子串合集 - 图43🍗最长公共子序列、最长公共子串合集 - 图44 的最长公共子序列,取两项中较大🍗最长公共子序列、最长公共子串合集 - 图45

由此可以得到状态转移方程:

🍗最长公共子序列、最长公共子串合集 - 图46

最终计算得到 🍗最长公共子序列、最长公共子串合集 - 图47 即为 🍗最长公共子序列、最长公共子串合集 - 图48🍗最长公共子序列、最长公共子串合集 - 图49 的最长公共子序列的长度。
🍗最长公共子序列、最长公共子串合集 - 图50

复杂度分析

时间复杂度:🍗最长公共子序列、最长公共子串合集 - 图51,其中 🍗最长公共子序列、最长公共子串合集 - 图52🍗最长公共子序列、最长公共子串合集 - 图53分别是字符串 🍗最长公共子序列、最长公共子串合集 - 图54🍗最长公共子序列、最长公共子串合集 - 图55

  1. - 二维数组 ![](https://cdn.nlark.com/yuque/__latex/527e8c62c64a12a0a5ffd17d02d83bdc.svg#card=math&code=%5Csmall%20%5Ctext%7Bdp%7D&id=HocX2) 有 ![](https://cdn.nlark.com/yuque/__latex/d6f147b9f168e0b5fbec1db2ccaa315b.svg#card=math&code=m%2B1&id=Quv7I)行和![](https://cdn.nlark.com/yuque/__latex/286ad2b5e996f314372ab315083e5718.svg#card=math&code=n%2B1&id=RGPSB)列,需要对 ![](https://cdn.nlark.com/yuque/__latex/527e8c62c64a12a0a5ffd17d02d83bdc.svg#card=math&code=%5Csmall%20%5Ctext%7Bdp%7D&id=KBkZj) 中的每个元素进行计算。

空间复杂度:🍗最长公共子序列、最长公共子串合集 - 图56,其中 🍗最长公共子序列、最长公共子串合集 - 图57🍗最长公共子序列、最长公共子串合集 - 图58分别是字符串 🍗最长公共子序列、最长公共子串合集 - 图59🍗最长公共子序列、最长公共子串合集 - 图60

  - 创建了 ![](https://cdn.nlark.com/yuque/__latex/d6f147b9f168e0b5fbec1db2ccaa315b.svg#card=math&code=m%2B1&id=tMCWd)行和![](https://cdn.nlark.com/yuque/__latex/286ad2b5e996f314372ab315083e5718.svg#card=math&code=n%2B1&id=CoehM)列的二维数组  ![](https://cdn.nlark.com/yuque/__latex/527e8c62c64a12a0a5ffd17d02d83bdc.svg#card=math&code=%5Csmall%20%5Ctext%7Bdp%7D&id=LuXcC) 

我的代码 [求解长度]

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        //1. 记录 m,n 长度
        int m = text1.length(), n = text2.length();
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            char c1 = text1.charAt(i - 1);
            for (int j = 1; j <= n; j++) {
                char c2 = text2.charAt(j - 1);
                //2. 如果相等
                if (c1 == c2) {
                    //在原来的基础上长度加1
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    //3. 不相等的话需要找到最大值
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[m][n];
    }
}

我的代码 [求解序列]

class Solution {
    public static String longestCommonSubsequence(String text1, String text2) {
        //1. 记录 m,n 长度
        int m = text1.length(), n = text2.length();
        String[][] dp = new String[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            char c1 = text1.charAt(i - 1);
            for (int j = 1; j <= n; j++) {
                char c2 = text2.charAt(j - 1);
                //2. 如果相等
                if (c1 == c2) {
                    //在原来的基础上长度加1
                    dp[i][j] = dp[i - 1][j - 1]==null? c1+"":dp[i - 1][j - 1]+c1;
                } else {
                    //3. 不相等的话需要找到最大值
                    dp[i][j] = ((dp[i - 1][j]==null?0:dp[i - 1][j].length()) >
                                (dp[i][j - 1]==null?0:dp[i][j - 1].length()))?
                                dp[i - 1][j]:dp[i][j - 1];
                }
            }
        }
        return dp[m][n];
    }

    public static void main(String[] args) {
        System.out.println(longestCommonSubsequence("abcde","ace"));//ace
    }
}

解题子串问题:动态规划

假设字符串 🍗最长公共子序列、最长公共子串合集 - 图61🍗最长公共子序列、最长公共子串合集 - 图62的长度分别为 🍗最长公共子序列、最长公共子串合集 - 图63🍗最长公共子序列、最长公共子串合集 - 图64 ,创建 🍗最长公共子序列、最长公共子串合集 - 图65🍗最长公共子序列、最长公共子串合集 - 图66 列的二维数组 🍗最长公共子序列、最长公共子串合集 - 图67

👉 定义 🍗最长公共子序列、最长公共子串合集 - 图68 表示为 🍗最长公共子序列、最长公共子串合集 - 图69🍗最长公共子序列、最长公共子串合集 - 图70 的最长公共子串的长度。

上述表示中 text1[0:i] 表示 text1 的长度为 i 的前缀,text2[0:j] 表示 text2 的长度为 j 的前缀。

考虑动态规划的边界情况:

  1. 🍗最长公共子序列、最长公共子串合集 - 图71 时,🍗最长公共子序列、最长公共子串合集 - 图72 为空,空字符串和任何字符串的最长公共子串的长度都是 🍗最长公共子序列、最长公共子串合集 - 图73,因此对任意 🍗最长公共子序列、最长公共子串合集 - 图74,有 🍗最长公共子序列、最长公共子串合集 - 图75
  2. 🍗最长公共子序列、最长公共子串合集 - 图76 时,🍗最长公共子序列、最长公共子串合集 - 图77 为空,空字符串和任何字符串的最长公共子串的长度都是🍗最长公共子序列、最长公共子串合集 - 图78,因此对任意 🍗最长公共子序列、最长公共子串合集 - 图79 ,有 🍗最长公共子序列、最长公共子串合集 - 图80

因此动态规划的边界情况是:当 🍗最长公共子序列、最长公共子串合集 - 图81🍗最长公共子序列、最长公共子串合集 - 图82 时,🍗最长公共子序列、最长公共子串合集 - 图83
🍗最长公共子序列、最长公共子串合集 - 图84🍗最长公共子序列、最长公共子串合集 - 图85 时,考虑 🍗最长公共子序列、最长公共子串合集 - 图86 的计算:

  1. 🍗最长公共子序列、最长公共子串合集 - 图87 时, 🍗最长公共子序列、最长公共子串合集 - 图88
    • 考虑 🍗最长公共子序列、最长公共子串合集 - 图89🍗最长公共子序列、最长公共子串合集 - 图90的最长公共子串,再增加一个字符(即公共字符)即可得到 🍗最长公共子序列、最长公共子串合集 - 图91🍗最长公共子序列、最长公共子串合集 - 图92的最长公共子串
  2. 🍗最长公共子序列、最长公共子串合集 - 图93 时, 🍗最长公共子序列、最长公共子串合集 - 图94

由此可以得到状态转移方程:

🍗最长公共子序列、最长公共子串合集 - 图95

最终计算得到 🍗最长公共子序列、最长公共子串合集 - 图96 即为 🍗最长公共子序列、最长公共子串合集 - 图97🍗最长公共子序列、最长公共子串合集 - 图98 的最长公共子串的长度。

复杂度分析

时间复杂度:🍗最长公共子序列、最长公共子串合集 - 图99,其中 🍗最长公共子序列、最长公共子串合集 - 图100🍗最长公共子序列、最长公共子串合集 - 图101分别是字符串 🍗最长公共子序列、最长公共子串合集 - 图102🍗最长公共子序列、最长公共子串合集 - 图103

  - 二维数组  ![](https://cdn.nlark.com/yuque/__latex/527e8c62c64a12a0a5ffd17d02d83bdc.svg#card=math&code=%5Csmall%20%5Ctext%7Bdp%7D&id=CabcV) 有 ![](https://cdn.nlark.com/yuque/__latex/d6f147b9f168e0b5fbec1db2ccaa315b.svg#card=math&code=m%2B1&id=WTMfI)行和![](https://cdn.nlark.com/yuque/__latex/286ad2b5e996f314372ab315083e5718.svg#card=math&code=n%2B1&id=xdzDo)列,需要对  ![](https://cdn.nlark.com/yuque/__latex/527e8c62c64a12a0a5ffd17d02d83bdc.svg#card=math&code=%5Csmall%20%5Ctext%7Bdp%7D&id=oaKlN) 中的每个元素进行计算。

空间复杂度:🍗最长公共子序列、最长公共子串合集 - 图104,其中 🍗最长公共子序列、最长公共子串合集 - 图105🍗最长公共子序列、最长公共子串合集 - 图106分别是字符串 🍗最长公共子序列、最长公共子串合集 - 图107🍗最长公共子序列、最长公共子串合集 - 图108

  - 创建了 ![](https://cdn.nlark.com/yuque/__latex/d6f147b9f168e0b5fbec1db2ccaa315b.svg#card=math&code=m%2B1&id=QQKBH)行和![](https://cdn.nlark.com/yuque/__latex/286ad2b5e996f314372ab315083e5718.svg#card=math&code=n%2B1&id=pFT4j)列的二维数组  ![](https://cdn.nlark.com/yuque/__latex/527e8c62c64a12a0a5ffd17d02d83bdc.svg#card=math&code=%5Csmall%20%5Ctext%7Bdp%7D&id=c7bgV) 

我的代码 [求解长度]

class Solution {
    public int longestCommonSubstring(String text1, String text2) {
        //1. 记录 m,n 长度
        int m = text1.length(), n = text2.length();
        int res=0;
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            char c1 = text1.charAt(i - 1);
            for (int j = 1; j <= n; j++) {
                char c2 = text2.charAt(j - 1);
                //2. 如果相等
                if (c1 == c2) {
                    //在原来的基础上长度加1
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    //3. 不相等的话需要找到最大值
                    dp[i][j] = 0;
                }
                res=Math.max(res,dp[i][j]);
            }
        }
        return res;
    }
}

我的代码 [求解序列]

class Solution {
    public static String longestCommonSubstring(String text1, String text2) {
        //1. 记录 m,n 长度
        int m = text1.length(), n = text2.length();
        String res="";
        String[][] dp = new String[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            char c1 = text1.charAt(i - 1);
            for (int j = 1; j <= n; j++) {
                char c2 = text2.charAt(j - 1);
                //2. 如果相等
                if (c1 == c2) {
                    //在原来的基础上长度加1
                    dp[i][j] = dp[i - 1][j - 1]==null? c1+"":dp[i - 1][j - 1]+c1;
                } else {
                    //3. 不相等的话需要找到最大值
                    dp[i][j] = "";
                }
                res=res.length()>dp[i][j].length()?res:dp[i][j];
            }
        }
        return res;
    }

    public static void main(String[] args) {
        System.out.println(longestCommonSubstring("aaacbcbceef","aaabcbceed"));//bcbcee
    }
}