代码随想录 | Day 56 - LeetCode 583. 两个字符串的删除操作、LeetCode 72. 编辑距离

        今天是“编辑距离”部分的最后一部分,有了前面的经验做起来还是容易很多。重点还是理解“删除”操作,具体包括删除其中哪一个,删除次数应该加多少次。另外这两道题的初始化部分,不再是全部初始化为0了。


        第1题(LeetCode 583. 两个字符串的删除操作)可以转换为是day 53中第1题(LeetCode 1143. 最长公共子序列)。如果能得到最长公共子序列的长度,那么两字符串的长度各自减去该长度,再相加,就得到删除次数。所以自己的实现方式与1143题相同。

class Solution {
public:
    int minDistance(string word1, string word2) {
        vector> dp(word1.size() + 1, vector(word2.size() + 1, 0));
        for (int i = 1; i <= word1.size(); ++i) {
            for (int j = 1; j <= word2.size(); ++j) {
                if (word1[i - 1] == word2[j - 1]) { // 不是text1[i] == text2[j]
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return word1.size() + word2.size() - 2 * dp[word1.size()][word2.size()];
    }
};

        题解还有另一种方法,就是依照题目把dp[i][j]就定义为“使得word1[0 : i - 1]和word1[0 : j - 1]变得相同需要的最小删除次数”(不用[0 : i]和[0 : j]的理由与之前一致,避免初始化的麻烦)。所以对于word1[i - 1]和word2[j - 1],仍然分两种情况。两者相等时,相对于word1[0 : i - 2]和word1[0 : j - 2]不需要额外的删除次数,所以删除次数仍然是dp[i - 1][j - 1];而两者不相等时,有三种可能:

  1. 将word1[i - 1]和word2[j - 1]都删掉(删除次数+2),对应dp[i - 1][j - 1] + 2;
  2. 只将word1[i - 1]删掉(删除次数+1),对应dp[i][j - 1] + 1;
  3. 只将word2[j - 1]删掉(删除次数+1),对应dp[i - 1][j] + 1。

对于第1种可能,由于dp[i - 1][j - 1] + 1要么与dp[i - 1][j]相等,要么与dp[i][j - 1]相等(因为删除次数+1对应多删1个元素,即多一个元素),所以dp[i - 1][j - 1] + 2等于dp[i - 1][j] + 1或dp[i][j - 1] + 1。那么“在三者中取较小值的操作”就可以优化为“在后两者中取较小值”。

        每个位置的dp值都依赖于其左方、上方和左上方,所以需要初始化第0行和第0列。第0行与第0列分别对应其中一个word为空的情境,所以需要赋值为另一个word的长度,也就是把另一个word全部删除所需要的次数。同样地,内外层循环的遍历方向都是正向。

class Solution {
public:
    int minDistance(string word1, string word2) {
        vector> dp(word1.size() + 1, vector(word2.size() + 1));
        for (int i = 0; i <= word1.size(); ++i) { // 不是i < word1.size()
            dp[i][0] = i;
        }
        for (int j = 0; j <= word2.size(); ++j) { // 不是j < word2.size()
            dp[0][j] = j;
        } 
        for (int i = 1; i <= word1.size(); ++i) {
            for (int j = 1; j <= word2.size(); ++j) {
                if (word1[i - 1] == word2[j - 1]) { // 不是text1[i] == text2[j]
                    dp[i][j] = dp[i - 1][j - 1];
                }
                else {
                    dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
                }
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

        需要注意初始化时的循环条件符号,不是<,而是<=。


        第2题(LeetCode 72. 编辑距离)是子序列问题部分的最后也是最难的一道题,不过有了前面的经验,自己还是做出来了。这道题相比上一题,只是将删除扩展为增、删、改了,其余地方一致。dp[i][j]定义为“使得word1[0 : i - 1]和word1[0 : j - 1]变得相同需要的最小编辑次数”(不用[0 : i]和[0 : j]的理由与之前一致,避免初始化的麻烦)。对于word1[i - 1]和word2[j - 1],依然分两种情况。两者相等时,相对于word1[0 : i - 2]和word1[0 : j - 2]不需要额外的编辑次数,所以编辑次数仍然是dp[i - 1][j - 1];而两者不相等时,有三种可能:

  1. 将word1[i - 1]改为word2[j - 1](“改”次数+2),对应dp[i - 1][j - 1] + 1;
  2. “只将word1[i - 1]删掉”或“只在word2[0 : j - 2]后增加1位word1[i - 1]”(“删”次数+1或“增”次数+1),对应dp[i][j - 1] + 1;
  3. “只将word2[j - 1]删掉”或“只在word1[0 : i - 2]后增加1位word2[j - 1]”(“删”次数+1或“增”次数+1),对应dp[i - 1][j] + 1。

从三者中取较小值作为当前结果。

        初始化和遍历顺序都与上一题相同。

class Solution {
public:
    int minDistance(string word1, string word2) {
        vector> dp(word1.size() + 1, vector(word2.size() + 1));
        for (int i = 0; i <= word1.size(); ++i) {
            dp[i][0] = i;
        }
        for (int j = 1; j <= word2.size(); ++j) {
            dp[0][j] = j;
        }
        for (int i = 1; i <= word1.size(); ++i) {
            for (int j = 1; j <= word2.size(); ++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 - 1] + 1, min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
                }
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

        其中第17行也可以像题解一样使用min({a, b, c})的方式。

你可能感兴趣的:(代码随想录,leetcode,算法,c++,数据结构,动态规划)