代码随想录算法训练营Day51|115.不同的子序列、583.两个字符串的删除操作、72.编辑距离

不同的子序列

115. 不同的子序列 - 力扣(LeetCode)

代码随想录 (programmercarl.com)

dp[i][j]:以i-1为结尾的s的子序列中出现以j-1为结尾的t的个数。

递推公式上,我们需要考虑s[i-1] == t[j-1]和s[i-1]!=t[j-1]两种情况

s[i-1] == t[j-1]时,有两种情形,1是使用s[i-1]来匹配,如s:bagg和t:bag,s[2] = s[3] = 'g',可以使用s[0]、s[1]、s[2]来实现t,也能用s[0]、S[1]、S[3]来实现t,所以当s[i-1] == t[j-1]时,

dp[i][j] = dp[i-1][j] + dp[i-1][j-1]

当s[i-1]!=t[j-1]时,dp[i][j] = dp[i-1][j],即不考虑使用s[i-1]来匹配(相当于模拟在s中删除这个元素)

dp数组由二维数组当前ij的左上和上方推导而来,因此需要初始化的值有dp[0][0],dp[i][0]。

dp[i][0]表示以i-1为结尾的s可以删除元素,出现空字符串的个数。因此dp[i][0]为0.

dp[0][j]表示空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。dp[0][j]为0

dp[0][0]表示空字符串s可以删除0个元素变成空字符串t.dp[0][0]为1.

由于值由左上和上方得到,所以遍历顺序为从上到下,再从左到右。

最后返回dp[s.size()][t.size()]

class Solution {
public:
    int numDistinct(string s, string t) {
        // 创建一个二维向量 dp,用于存储动态规划的状态值
        // dp[i][j] 表示 s 的前 i 个字符和 t 的前 j 个字符的子序列出现次数
        // 初始化 dp 的大小为 (s.size()+1) x (t.size()+1),值全部为 0
        vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1, 0));

        // 初始化 dp 数组的边界值
        // dp[i][0] 表示 s 的前 i 个字符和空字符串的子序列出现次数,即 1
        // dp[0][j] 表示空字符串和 t 的前 j 个字符的子序列出现次数,即 0
        for (int i = 0; i < s.size(); i++) {
            dp[i][0] = 1;
        }
        for (int j = 1; j < t.size(); j++) {
            dp[0][j] = 0;
        }

        // 双层循环遍历 s 和 t
        for (int i = 1; i <= s.size(); i++) {  // i 从 1 开始,直到 s.size()
            for (int j = 1; j <= t.size(); j++) {  // j 从 1 开始,直到 t.size()
                // 如果 s 的第 i 个字符和 t 的第 j 个字符相同
                if (s[i - 1] == t[j - 1]) {
                    // 则 dp[i][j] 等于 dp[i-1][j-1] 加 dp[i-1][j]
                    // 这表示我们可以将 s 的前 i-1 个字符与 t 的前 j-1 个字符的子序列
                    // 加上 s 的第 i 个字符,形成 s 的前 i 个字符和 t 的前 j 个字符的子序列
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                } else {
                    // 如果字符不同,则 dp[i][j] 等于 dp[i-1][j]
                    // 这表示我们无法将 s 的前 i-1 个字符与 t 的前 j-1 个字符的子序列
                    // 加上 s 的第 i 个字符,形成 s 的前 i 个字符和 t 的前 j 个字符的子序列
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        // 返回 dp[s.size()][t.size()]
        return dp[s.size()][t.size()];
    }
};

算法的时间复杂度为O(m*n),空间复杂度为O(m*n)

两个字符串的删除操作

583. 两个字符串的删除操作 - 力扣(LeetCode)

寻找最长的公共子序列,然后删去与公共子序列不同的部分。

class Solution {
public:
    int minDistance(string word1, string word2) {
        // 创建一个二维向量 dp,用于存储动态规划的状态值
        // 初始化 dp 的大小为 (word1.size()+1) x (word2.size()+1),值全部为 0
        vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1, 0));

        // 双层循环遍历 word1 和 word2
        for (int i = 1; i <= word1.size(); i++) {  // i 从 1 开始,直到 word1.size()
            for (int j = 1; j <= word2.size(); j++) {  // j 从 1 开始,直到 word2.size()
                // 如果 word1 的第 i 个字符和 word2 的第 j 个字符相同
                if (word1[i - 1] == word2[j - 1]) {
                    // 则 dp[i][j] 等于 dp[i-1][j-1] 加 1
                    // 这表示我们可以将 word1 的前 i-1 个字符转换为 word2 的前 j-1 个字符
                    // 然后添加 word1 的第 i 个字符,即 word1 的第 i 个字符和 word2 的第 j 个字符相同
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    // 如果字符不同,则 dp[i][j] 等于 dp[i-1][j] 和 dp[i][j-1] 中的较大值
                    // 这表示我们可以在不替换字符的情况下插入或删除一个字符,或者替换一个字符
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        // 返回 length - 2*dp[word1.size()][word2.size()],即编辑距离
        return length - 2 * dp[word1.size()][word2.size()];
    }
};

算法的时间复杂度为O(m*n),空间复杂度为O(m*n)

编辑距离

72. 编辑距离 - 力扣(LeetCode)

dp[i][j]表示将word1前i个字符转换为word2前j个字符所需的最少操作次数。

当word1[i-1] == word2[j-1]时,dp[i][j] = dp[i-1][j-1],由于当前两个字符相同,则转换的最少操作次数和之前相同。

当word1[i-1]!=word2[j-1]时,给出了三个操作,分别是添加、删除和修改,其中,添加和删除是一体两面的,word1的添加等同于word2的删除,反之亦然,此时dp[i][j] = dp[i-1][j] + 1、dp[i][j] = dp[i][j-1] + 1,修改即将word1[i-1]改为word2[j-1],此时,dp[i][j] = dp[i-1][j-1] + 1。

dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1。

dp[i][0]表示将word1变成空字符需要几步,dp[i][0] = i;

dp[0][j]表示将word2变成空字符需要几步,dp[0][j] = j;

dp[0][0] = 0;

从左到右从上到下进行遍历。

最后返回dp[word1.size()][word2.size()]。

class Solution {
public:
    int minDistance(string word1, string word2) {
        // 创建一个二维向量 dp,用于存储动态规划的状态值
        // dp[i][j] 表示将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最少编辑操作次数
        vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1, 0));

        // 初始化 dp 数组的边界值
        // dp[i][0] 表示将 word1 的前 i 个字符转换为空字符串所需的最少编辑操作次数,即 i
        // dp[0][j] 表示将空字符串转换为 word2 的前 j 个字符所需的最少编辑操作次数,即 j
        for (int i = 0; i <= word1.size(); i++) {
            dp[i][0] = i;
        }
        for (int j = 0; j <= word2.size(); j++) {
            dp[0][j] = j;
        }

        // 双层循环遍历 word1 和 word2
        for (int i = 1; i <= word1.size(); i++) {  // i 从 1 开始
            for (int j = 1; j <= word2.size(); j++) {  // j 从 1 开始
                // 如果 word1 的第 i 个字符和 word2 的第 j 个字符相同
                if (word1[i - 1] == word2[j - 1]) {
                    // 则 dp[i][j] 等于 dp[i-1][j-1]
                    // 这表示我们可以将 word1 的前 i-1 个字符转换为 word2 的前 j-1 个字符
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    // 如果字符不同,则 dp[i][j] 等于 dp[i-1][j-1]、dp[i-1][j] 和 dp[i][j-1] 中的最小值加 1
                    // 这表示我们可以在不替换字符的情况下插入或删除一个字符,或者替换一个字符
                    dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
                }
            }
        }
        // 返回 dp[word1.size()][word2.size()],即编辑距离
        return dp[word1.size()][word2.size()];
    }
};

算法的时间复杂度为O(m*n),空间复杂度为O(m*n)

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值