【Day39 LeetCode】动态规划DP 编辑问题

一、动态规划DP 编辑问题

1、不同的子序列 115

采用动态规划来解决子序列问题。有两个字符串,采用二维dp数组,dp[i][j]表示0~i-1的s子字符串中含有0 ~j-1的t子字符串的个数。

递推公式分为s[i - 1] 与 t[j - 1]相等和不相等的情况

CPP 复制代码
class Solution {
public:
    int numDistinct(string s, string t) {
        int m = s.size(), n = t.size();
        vector<vector<unsigned long long>> dp(m + 1, vector<unsigned long long>(n + 1));
        for(int i=0; i<=m; ++i)
            dp[i][0] = 1;
        for(int i=1; i<=m; ++i){
            for(int j=1; j<=n; ++j){
                dp[i][j] = dp[i-1][j];
                if(s[i-1]==t[j-1])
                    dp[i][j] += dp[i-1][j-1];
            }
        }
        return dp[m][n];
    }
};

由递推公式可知,当前值dp[i][j]只与其上方的值和左上方值有关,可以将二维数组优化成一维数组,代码如下

CPP 复制代码
class Solution {
public:
    int numDistinct(string s, string t) {
        int m = s.size(), n = t.size();
        vector<unsigned long long> dp(n + 1, 0);
        dp[0] = 1;
        for(int i=1; i<=m; ++i){
            int leftup = dp[0];
            for(int j=1; j<=n; ++j){
                int tmp = dp[j];
                if(s[i-1]==t[j-1])
                    dp[j] += leftup;
                leftup = tmp;
            }
        }
        return dp[n];
    }
};

2、两个字符串的删除操作 583

相比于上一题,区别就是两个字符串都可以进行删除。

所以在推导递推公式时,当word1[i-1]==word2[j-1], d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] dp[i][j] = dp[i-1][j-1] dp[i][j]=dp[i−1][j−1];当word1[i-1]!=word2[j-1], d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + 1 dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + 1 dp[i][j]=min(dp[i−1][j],dp[i][j−1])+1,会从删除word1[i-1]或者word2[j-1]之后取最小的结果,同时+1(删除操作)。

CPP 复制代码
class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size(), n = word2.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        for(int i=0; i<=m; ++i)
            dp[i][0] = i;
        for(int i=1; i<=n; ++i)
            dp[0][i] = i;
        for(int i=1; i<=m; ++i){
            for(int j=1; j<=n; ++j){
                if(word1[i-1]==word2[j-1])
                    dp[i][j] = dp[i-1][j-1];
                else
                    dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + 1;
            }
        }
        return dp[m][n];
    }
};

空间复杂度优化,思路和之前提到的一样,采用变量记录左上方的值,代码如下:

CPP 复制代码
class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size(), n = word2.size();
        vector<int> dp(n + 1);
        for(int i=1; i<=n; ++i)
            dp[i] = i;
        for(int i=1; i<=m; ++i){
            int leftup = dp[0];
            dp[0] = i;
            for(int j=1; j<=n; ++j){
                int tmp = dp[j];
                if(word1[i-1]==word2[j-1])
                    dp[j] = leftup;
                else
                    dp[j] = min(dp[j], dp[j-1]) + 1;
                leftup = tmp;
            }
        }
        return dp[n];
    }
};

3、编辑距离 72

直接DP,dp数组dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2的最近编辑距离。

递推公式推导,当word1[i - 1] == word2[j - 1]时,表示不需要进行操作, d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] dp[i][j] = dp[i-1][j-1] dp[i][j]=dp[i−1][j−1];当word1[i - 1] != word2[j - 1]时,我们可以对word2[j-1]进行删除(dp[i][j-1]),也可以对word1[i-1]进行删除(dp[i-1][j]), d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] dp[i][j] = dp[i-1][j-1] dp[i][j]=dp[i−1][j−1];还有将word1[i-1]或者word2[j-1]替换(dp[i-1][j-1]),所以 d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j − 1 ] , m i n ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) ) + 1 dp[i][j] = min(dp[i-1][j-1], min(dp[i-1][j], dp[i][j-1])) + 1 dp[i][j]=min(dp[i−1][j−1],min(dp[i−1][j],dp[i][j−1]))+1

CPP 复制代码
class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size(), n = word2.size();
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        for(int i=1; i<=m; ++i)
            dp[i][0] = i; // 执行插入操作即可
        for(int i=1; i<=n; ++i)
            dp[0][i] = i;
        for(int i=1; i<=m; ++i){
            for(int j=1; j<=n; ++j){
                if(word1[i-1]==word2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                }else{
                    dp[i][j] = min(dp[i-1][j], min(dp[i][j-1], dp[i-1][j-1])) + 1;
                }
            }
        }
        return dp[m][n];
    }
};

空间复杂度优化,优化思路和之前一样,需要注意边界的初始化,代码如下:

CPP 复制代码
class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size(), n = word2.size();
        vector<int> dp(n+1);
        for(int i=1; i<=n; ++i)
            dp[i] = i;
        for(int i=1; i<=m; ++i){
            int leftup = dp[0];
            dp[0] = i;
            for(int j=1; j<=n; ++j){
                int tmp = dp[j];
                if(word1[i-1]==word2[j-1]){
                    dp[j] = leftup;
                }else{
                    dp[j] = min(dp[j], min(dp[j-1], leftup)) + 1;
                }
                leftup = tmp;
            }
        }
        return dp[n];
    }
};

二、写在后面

字符串的编辑问题需要搞清楚是只能编辑一个字符串,还是两个字符串都可以编辑,这决定了递推公式的写法;同时需要注意初始化问题。另外,二维数组转一维数组较为套路化,需要细心。

相关推荐
郑州吴彦祖7727 分钟前
数据结构——二叉树经典习题讲解
java·数据结构·算法·leetcode
lyx1426068 分钟前
leetcode 8. 字符串转换整数 (atoi)
算法
qy发大财10 分钟前
跳跃游戏II(力扣45)
算法·leetcode
Joyner201814 分钟前
python-leetcode-相交链表
算法·leetcode·链表
和光同尘@40 分钟前
74. 搜索二维矩阵(LeetCode 热题 100)
数据结构·c++·线性代数·算法·leetcode·职场和发展·矩阵
一去不复返的通信er42 分钟前
SVD预编码
算法·信息与通信·预编码算法·通信原理
柠石榴1 小时前
【练习】【二分】力扣热题100 34. 在排序数组中查找元素的第一个和最后一个位置
c++·算法·leetcode·二分
Tisfy2 小时前
LeetCode 2209.用地毯覆盖后的最少白色砖块:记忆化搜索之——深度优先搜索(DFS)
算法·leetcode·深度优先·dfs·题解·记忆化搜索·深度优先搜索
Trouvaille ~2 小时前
【C++篇】树影摇曳,旋转无声:探寻AVL树的平衡之道
数据结构·c++·算法·蓝桥杯·计算机科学·平衡二叉树·avl
陈浩源同学2 小时前
学习 TypeScript 栈和队列数据结构
前端·算法