代码随想录算法训练营第五十六天|583. 两个字符串的删除操作、72. 编辑距离

LeetCode 583. 两个字符串的删除操作

链接:583. 两个字符串的删除操作

思路:

与上一题115. 不同的子序列稍有相似之处,还是需要找到两个字符串的共同部分,在此基础上这道题目还需要将不同部分删除掉,然后记录下删除操作的次数。定义下标:dp[i][j]表示使得长度为i的word1的子字符串和长度为j的word2的子字符串相同所需的最小步数。然后根据定义初始化dp数组。当其中一个字符串为0的时候,所需要删除的次数就等于另一个字符串的长度,所以有:

  • dp[i][0] = i:当word2长度为0时,删除的次数等于word1的长度i
  • dp[0][j] = j:当word1长度为0时,删除的次数等于word2的长度j

接下来是确定递推公式。为了得到长度分别为i和j的子字符串的最小操作数dp[i][j],我们可以比较word1的第i-1个字符和word2的第j-1个字符,如果它们正好相等,那我们就不用做任何操作,这时的操作数等于长度分别为i-1和j-1的子字符串的最小操作数dp[i-1][j-1],所以当word1[i-1]等于word2[j-1]时,有dp[i][j] = dp[i-1][j-1]。

再考虑第i-1个字符和第j-1个字符不相等的情况,如果它们不相等,那就表示需要做删除操作,我们既可以从word1删除一个字符,也可以从word2删除一个字符,前者表示word1的长度为i-2,word2的长度为j-1的操作数,即dp[i-1][j],在此基础上删除一个字符的话就需要增加一个操作数,所以是dp[i-1][j] + 1,同理后者可得dp[i][j-1] + 1,最后我们需要在这两个操作之间找一个最小值,也就是dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1)。这就是完整的递推公式,我们把相等和不相等的两种情况都考虑了。

最后返回dp数组右下角的值dp[word1.size()][word2.size()]。

代码:

class Solution {
public:
    int minDistance(string word1, string word2) {
        // dp[i][j]表示使得长度为i的word1的子字符串和长度为j的word2的子字符串相同所需的最小步数
        vector> dp(word1.size() + 1, vector(word2.size() + 1));
        for (int i = 1; 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, dp[i][j-1] + 1);
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

LeetCode 72. 编辑距离

链接:72. 编辑距离

思路:

动态规划经典题目,还是相当有难度的,但是经过前面几道题的铺垫,这题也没有那么难了。首先还是定义下标,dp[i][j]表示使得长度为i的word1的子字符串和长度为j的word2的子字符串相同所需的最小编辑距离。然后初始化dp数组,可以发现当其中一个字符串的长度为0时,它们的编辑距离就等于另一个字符串的长度,因为不管是给长度为0的字符串做增加操作,还是删减另一个字符串的长度,所需的操作数都是一样的,所以有:

  • dp[i][0] = i
  • dp[0][j] = j

接下来最重要的还是递推公式。本题有三种操作方式,即插入一个字符,删除一个字符,替换一个字符,当然也可以什么都不操作,这也就表示了想要到达dp[i][j],可以在不同的状态通过以上四种操作到达。根据状态不同,可以分为以下两种:

  1. word1[i-1] == word2[j-1]:这就表示word1的第i-1个字符和word2的第j-1个字符相等,很好,这时我们不需要做任何操作,就像上题一样,可以推出dp[i][j] = dp[i-1][j-1]。
  2. word1[i-1] != word2[j-1]:这个时候情况变得复杂起来,因为可以进行的操作变多了。
    1. 首先考虑替换操作。由于替换操作是直接在长度为i和j的子字符串上修改,那么其操作数就等于长度分别为i-1和j-1的子字符串的最小操作数dp[i-1][j-1],再加上一次替换操作。我们有exchange = dp[i-1][j-1] + 1,exchange表示替换的意思。
    2. 删除操作。此时删除操作和上一题583. 两个字符串的删除操作完全一样,不做过多解释,所以del = min(dp[i-1][j] + 1, dp[i][j-1] + 1),del表示删除的意思。
    3. 最后为插入操作。这个时候如果真的去模拟插入字符操作,会变得相当麻烦,首先我们的dp数组长度是固定的,如果贸然往里面添加字符,可能会导致数组越界,递推公式也会变得十分不好写,不妨用逆向思维:如果从word1删除一个字符,不就是相当于word1不变,从word2添加一个字符吗?这样就把插入操作转化为了删除操作,而删除操作的递推公式我们之前已经写过了。

以上就是全部的递推公式,最后要在这三种操作之间找一个最小值,也就是dp[i][j] = min(addel, exchange)。最后返回dp数组的最右下值dp[word1.size()][word2.size()]。

代码:

class Solution {
public:
    int minDistance(string word1, string word2) {
        // dp[i][j]表示使得长度为i的word1的子字符串和长度为j的word2的子字符串相同所需的最小编辑距离
        vector> dp(word1.size() + 1, vector(word2.size() + 1));
        for (int i = 1; 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
                {
                    // 增删
                    int del = min(dp[i-1][j] + 1, dp[i][j-1] + 1);
                    // 换
                    int exchange = dp[i-1][j-1] + 1;
                    dp[i][j] = min(del, exchange);
                }
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

你可能感兴趣的:(代码随想录算法训练营,算法,leetcode,动态规划)