编程题-最长公共子序列(中等-二维动态规划(重点))

题目:

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

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

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

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

前言:

假设字符串test1和字符串test1的长度分别为m和n,创建m+1行n+1列的二维数组dp,其中dp[i][j]表示text1[0:i]和text2[0:j]的最长公共子序列的长度。上述表示中,text1[0:i]表示text1的长度为i的前缀,text2[0:j]表示text2的长度为j的前缀。

考虑动态规划的边界情况:当i=0时,text1[0:i]为空,空字符串和任何字符串的最长公共子序列的长度都是0,因此对任意0≤j≤n,有dp[0][j]=0;同理当j=0时,dp[i][0]=0。因此动态规划的边界情况是:当i=0或j=0时,dp[i][j]=0。

当i>0且j>0时,考虑dp[i][j]的计算:

1、当text1[i-1]=text2[j-1]时,将这两个相同的字符称为公共字符,考虑text1[0:i-1]和text2[0:j-1]的最长公共子序列,再增加一个字符(即公共字符)即可得到text1[0:i]和text2[0:j]的最长公共子序列,因此dp[i][j]=dp[i-1][j-1]+1。

2、当text1[i-1]≠text2[j-1]时,考虑以下两项:text1[0:i-1]和text2[0:j]的最长公共子序列;text1[0:i]和text2[0:j-1]的最长公共子序列。要得到text1[0:i]和text2[0:j]的最长公共子序列,应取两项中的长度较大的一项,因此dp[i][j] = max(dp[i-1][j], dp[i][j-1])。

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

dp[i][j]=\left\{\begin{matrix}dp[i-1][j-1]+1, text1[i-1]=text2[j-1] \\ max(dp[i-1][j],dp[i][j-1]), text1[i-1]\neq text2[j-1] \end{matrix}\right.

最终计算得到dp[m][n]即为text1和text2的最长公共子序列的长度。

解法一(二维动态规划-迭代I):

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        //计算text1和text2的长度
        int m = text1.length(), n = text2.length();
        //定义两个text的最长公共子序列矩阵dp[i][j]。
        //其中i是text1第i个字符结尾,j是text2第j个字符结尾。初始值dp[i][j]均为0。
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        //依次循环遍历text1和text2第i位和第j位的值,逐渐递增更新dp[i][j]的值。
        for (int i = 1; i <= m; i++) {
            char c1 = text1.at(i - 1);
            for (int j = 1; j <= n; j++) {
                char c2 = text2.at(j - 1);
                //当c1==c2时,根据状态转移方程,进行赋值更新
                if (c1 == c2) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } 
                //当c1≠c2时,根据状态转移方程,进行赋值更新
                else {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[m][n];
    }
};

时间复杂度:O(mn),其中 m 和 n 分别是字符串 text 1和 text 2的长度。二维数组 dp 有 m+1 行和 n+1 列,需要对 dp 中的每个元素进行计算。空间复杂度:O(mn),其中 m 和 n 分别是字符串 text 1和 text 2的长度。创建了 m+1 行 n+1 列的二维数组 dp。

解法二(二维动态规划-递归I):

通过递归思想实现自顶向下的遍历,如下为实现代码:

class Solution {
public:
    int longestCommonSubsequence(string s, string t) {
        int n = s.length(), m = t.length();
        //定义以text1中i结尾和text2中j结尾的最长公共子序列关系矩阵
        vector memo(n, vector<int>(m, -1)); // -1 表示没有计算过
        auto dfs = [&](this auto&& dfs, int i, int j) -> int {
            if (i < 0 || j < 0) {
                return 0;
            }
            //防止重复计算导致时间复杂度升高,因此需要对res是否为-1进行判断
            int& res = memo[i][j]; // 注意这里是引用
            if (res != -1) {
                return res; // 之前计算过,则不再需要再次进行计算了
            }
            if (s[i] == t[j]) {
                return res = dfs(i - 1, j - 1) + 1;
            }
            return res = max(dfs(i - 1, j), dfs(i, j - 1));
        };
        return dfs(n - 1, m - 1);
    }
};

时间复杂度:O(mn),其中 m 和 n 分别是字符串 text 1和 text 2的长度。二维数组 dp 有 m+1 行和 n+1 列,需要对 dp 中的每个元素进行计算。空间复杂度:O(mn),其中 m 和 n 分别是字符串 text 1和 text 2的长度。创建了 m+1 行 n+1 列的二维数组 dp。

笔者小记:

1、动态规划方法的实现需要确定两个必要条件,一个是边界条件;另一个是状态转移方程。可以通过迭代或递归的方式实现。当满足if条件语句时,激活状态转移方程。

2、关于字符串根据索引值获取字符的操作:at()operator[] 比较

  • operator[] 不进行越界检查。如果索引越界,会导致程序崩溃或未定义行为。

  • at() 会进行越界检查,如果访问的索引无效,它会抛出一个 std::out_of_range 异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值