leetCode 1143.最长公共子序列 一步步思考动态规划 + 优化空间复杂度

leetCode 1143.最长公共子序列 动态规划 + 滚动数组-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/133689692?spm=1001.2014.3001.5501大家可以在我上期的文章中看此题目,接下来具体来详细表述如何一步步思考动态规划,以及优化空间复杂度🤔

一、递归搜索 + 保存计算结果 = 记忆化搜索

把这两个字符串分别叫做 st ,长度设为 nm ,和背包问题一样。子序列也相当于是考虑每个字母, 你是选还是不选从最后一个字母 开始考虑的话,两两组合就有这四种情况了 ,比如都不选,那么问题就从n个字母 + m个字母的原问题变成n-1个字母+m-1个字母的子问题了。那再一般化,考虑 s[i] 和 t[j] 选或不选 ,这样就确定了递归参数中的 i 和 j 表示的子问题 。就是 s 的前 i 个字母和 t 的前 j 个字母的LCS长度,那根据选或不选,就可以得到这样的一些子问题了。注意都选和都不选,它们的子问题是一样的 ,只有**s[i] = t[j]**的时候才能都选,当然这种情况下都选肯定是比都不选要好的,这样就可以得到两个式子了。

>>思考两个问题(O_O)?:

  • 第一个问题:在s[i] = t[j]的时候,我们需要考虑只选其中一个的情况吗?
  • 第二个问题:在s[i] ≠ t[j]的时候,我们需要考虑都不选的情况吗?

第一个问题,比如s=abcdc和t=abc,此时s和t的c是相等的 ,那么都选就变成它们的LCS长度+1了,把它的LCS长度记作x,即x = dfs(i-1,j-1)。如果不选s中的c的话,就变成 abcd, abc这种情况,如果它更优,就表示dfs(i-1,j)比x+1还要大,即dfs(i-1,j) > x+1,那么这两个c匹配的话,再把他两去掉,就变成了abd和ab。根据前面的假设,abd和ab的LCS长度是大于x的,同时由于这两个又是abcd和ab的子序列,所以它们两的LCS长度又是小于等于x的,这样就矛盾了。所以,dfs(i-1,j)它是小于等于x+1的,对于dfs(i,j-1)也同理。所以在s[i]=t[j]的时候,我们只需要考虑都选的情况就好了。

第二个问题,在s[i] ≠ t[j]的时候,需不需要调用dfs(i-1,j-1)呢?**其实是不需要的,因为在dfs(i-1,j)里面,如果它不选t[j]的话,那就回递归到dfs(i-1,j-1)了,那相当于dfs(i-1,j-1)的结果,已经在dfs(i-1,j)里面了,**或者说dfs(i-1,j)是大于等于dfs(i-1,j-1)的。那对于dfs(i,j-1)也是同理的,所以我们不需要递归到dfs(i-1,j-1)这里,那最终就可简化成这个式子了。

cpp 复制代码
class Solution {
public:
    int longestCommonSubsequence(string s, string t) {
        int n = s.length(), m = t.length(), cache[n][m];
        memset(cache, -1, sizeof(cache)); // -1 表示没有访问过
        function<int(int, int)> dfs = [&](int i, int j) -> int {
            if (i < 0 || j < 0) return 0;
            int &res = cache[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);
    }
};

作者:灵茶山艾府
链接:https://leetcode.cn/problems/longest-common-subsequence/solutions/2133188/jiao-ni-yi-bu-bu-si-kao-dong-tai-gui-hua-lbz5/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 时间复杂度:O(nm)
  • 空间复杂度:O(nm)

二、翻译成递推

cpp 复制代码
class Solution {
public:
    int longestCommonSubsequence(string s, string t) {
        int n = s.length(), m = t.length(), f[n + 1][m + 1];
        memset(f, 0, sizeof(f));
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                f[i + 1][j + 1] = s[i] == t[j] ? f[i][j] + 1 : max(f[i][j + 1], f[i + 1][j]);
        return f[n][m];
    }
};

作者:灵茶山艾府
链接:https://leetcode.cn/problems/longest-common-subsequence/solutions/2133188/jiao-ni-yi-bu-bu-si-kao-dong-tai-gui-hua-lbz5/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 时间复杂度:O(nm)
  • 空间复杂度:O(m)

三、空间优化:两个数组(滚动数组)

cpp 复制代码
class Solution {
public:
    int longestCommonSubsequence(string s,string t) {
        int n = s.length(),m = t.length(),f[2][m+1];
        memset(f,0,sizeof(f));
        for(int i=0;i<n;++i) {
            for(int j=0;j<m;++j) {
                if(s[i] == t[j]) f[(i+1)%2][j+1] = f[i%2][j] + 1;
                else f[(i+1)%2][j+1] = max(f[i%2][j+1],f[(i+1)%2][j]);
            } 
        }
        return f[n%2][m];
    }
};

四、空间优化:一个数组(滚动数组,优化空间复杂度)

从这个递推式可看出,对于每个状态,只需要知道它左边,上面和左上,这三个相邻方向的状态那么为了在递推的过程中,这三个方向都是经过计算的数值 ,所以要 从前向后 从上到下 来遍历这个矩阵

如果只有一个一维数组,那么再算当前行的时候,它的左上这个状态就会被之前的计算给覆盖掉,那如何解决这个问题呢?我们可以用一个临时变量**pre(previos)**把它记录起来。

cpp 复制代码
class Solution {
public:
    // 滚动数组
    int longestCommonSubsequence(string s,string t) {
        int m = t.length(),f[m+1];
        memset(f,0,sizeof(f));
        for(char x:s){
            for(int j=0,pre=0;j<m;++j) {
                int tmp = f[j+1];
                if(x == t[j]) f[j+1] = pre+1;
                else f[j+1] = max(f[j+1],f[j]);
                pre = tmp;
            }
        }
        return f[m];
    }
};
  • 时间复杂度:O(nm)
  • 空间复杂度:O(m)

参考文章和视频:

1143. 最长公共子序列 - 力扣(LeetCode)

最长公共子序列 编辑距离_哔哩哔哩_bilibili

相关推荐
爱研究的小陈44 分钟前
Day 3:数学基础回顾——线性代数与概率论在AI中的核心作用
算法
渭雨轻尘_学习计算机ing1 小时前
二叉树的最大宽度计算
算法·面试
BB_CC_DD1 小时前
四. 以Annoy算法建树的方式聚类清洗图像数据集,一次建树,无限次聚类搜索,提升聚类搜索效率。(附完整代码)
深度学习·算法·聚类
梁下轻语的秋缘3 小时前
每日c/c++题 备战蓝桥杯 ([洛谷 P1226] 快速幂求模题解)
c++·算法·蓝桥杯
CODE_RabbitV3 小时前
【深度强化学习 DRL 快速实践】逆向强化学习算法 (IRL)
算法
mit6.8243 小时前
[贪心_7] 最优除法 | 跳跃游戏 II | 加油站
数据结构·算法·leetcode
keep intensify3 小时前
通讯录完善版本(详细讲解+源码)
c语言·开发语言·数据结构·算法
shix .3 小时前
2025年PTA天梯赛正式赛 | 算法竞赛,题目详解
数据结构·算法
风铃儿~4 小时前
Java面试高频问题(26-28)
java·算法·面试
wuqingshun3141594 小时前
蓝桥杯 4. 卡片换位
算法·职场和发展·蓝桥杯