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

相关推荐
劲夫学编程40 分钟前
leetcode:杨辉三角
算法·leetcode·职场和发展
毕竟秋山澪43 分钟前
孤岛的总面积(Dfs C#
算法·深度优先
浮生如梦_3 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测
励志成为嵌入式工程师5 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
师太,答应老衲吧5 小时前
SQL实战训练之,力扣:2020. 无流量的帐户数(递归)
数据库·sql·leetcode
捕鲸叉5 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer5 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
wheeldown6 小时前
【数据结构】选择排序
数据结构·算法·排序算法
观音山保我别报错7 小时前
C语言扫雷小游戏
c语言·开发语言·算法
TangKenny8 小时前
计算网络信号
java·算法·华为