【一看就会一写就废 指间算法】最长公共子序列 —— 动态规划

78 阅读3分钟

指尖划过的轨迹,藏着最细腻的答案~

题目:

给定两个字符串 text1text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如, "ace""abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:

输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace" ,它的长度为 3 。

示例 2:

输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。

示例 3:

输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。

提示:

  • 1 <= text1.length, text2.length <= 1000
  • text1text2 仅由小写英文字符组成。

分析:

动态规划一般分为3步走:

  • 确定dp数组含义: 我们定义dp[i][j]text1从下标(0, i)与text2从下标(0,j)的最长的公共子序列。最终答案即为dp[m-1][n-1]

  • 状态转移方程:

    对于dp[i][j]来说:

    • 如果text1[i] == text2[j],两个字符串的字符相同,则最长公共子序列必定加1;
    • 如果text1[i] != text2[j],我们思考怎样可以到达dp[i][j]呢?从i-1到达i,即text1向后遍历一位,或者从j-1到达j,即tex2向后遍历一位,因此此时的最大的公共子序列为dp[i-1][j]dp[i][j-1]中的最大值。

    最终的状态转移方程如下: dp[i][j]={dp[i1][j1]+1,text1[i]==text2[j]max(dp[i1][j],dp[i][j1]),text1[i]!=text2[j]dp[i][j]= \begin{cases} dp[i-1][j-1] + 1,text1[i] == text2[j] \\ max(dp[i-1][j], dp[i][j-1]), text1[i] != text2[j] \end{cases}

  • 初始化: 如果i=0j=0时,上面状态转移方程不合法。因此我们可以在i=0的上面与j=0左边增加新的一行或一列数据,此时上面状态转移方程i需要变为i+1i-1变为i,如下: dp[i+1][j+1]={dp[i][j]+1,text1[i]==text2[j]max(dp[i][j+1],dp[i+1][j]),text1[i]!=text2[j]dp[i+1][j+1]= \begin{cases} dp[i][j] + 1,text1[i] == text2[j] \\ max(dp[i][j+1], dp[i+1][j]), text1[i] != text2[j] \end{cases}

AC代码:

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int m = text1.size(), n = text2.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (text1[i] == text2[j]) {
                    dp[i+1][j+1] = dp[i][j] + 1;
                } else {
                    dp[i+1][j+1] = max(dp[i+1][j], dp[i][j+1]);
                }
            }
        }

        return dp[m][n];
    }
};

空间优化一(两个数组):

我们可以观察到上面的状态转移方程只与i-1j-1有关,因此我们可以使用两个数组来轮转记录前一次的状态。

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int m = text1.size(), n = text2.size();
        vector<vector<int>> dp(2, vector<int>(n + 1, 0));

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (text1[i] == text2[j]) {
                    dp[(i+1) % 2][j+1] = dp[i % 2][j] + 1;
                } else {
                    dp[(i+1) % 2][j+1] = max(dp[(i+1) % 2][j], dp[i % 2][j+1]);
                }
            }
        }

        return dp[m % 2][n];
    }
};