583. 两个字符串的删除操作 (delete operation for two strings)

3,820 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第25天,点击查看活动详情

583. 两个字符串的删除操作 题目描述:给定两个单词 word1 和 word2 ,返回使得 word1 和  word2 相同所需的最小步数每步 可以删除任意一个字符串中的一个字符。提示 1 <= word1.length, word2.length <= 500word1 和 word2 只包含小写英文字母。

示例1示例2
输入: word1 = "sea", word2 = "eat"
输出: 22
解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat" 变为 "ea"
输入word1 = "leetcode", word2 = "etco"
输出44

中规中矩的动态规划

关于编辑距离问题,之前已经介绍过 # 115. 不同的子序列# 392. 判断子序列 两篇,这一题又是一个典型的编辑距离问题,只是两个字符串上的字符都可以进行删除操作。

1、确定 dp 状态数组

定义 dp[i][j]dp[i][j] 是以 i1i - 1 字符结尾的 word1 和 以 j1j - 1 字符结尾的 word2,使其相等时,需要删除元素的最小次数,其中 i[1,len1],j[1,len2]i \in [1, len_1], j \in [1, len_2]len1=word1.length,len2=word2.lengthlen_1 = word1.length,len_2 = word2.length

2、确定 dp 状态方程

依然要讨论 word1[i1]word1[i - 1]word2[j1]word2[j - 1] 是否相等:

word1[i1]==word2[j1]word1[i - 1] == word2[j - 1] 时,两字符相同说明不需要删除,以 i1i - 1 字符结尾的 word1 和 以 j1j - 1 字符结尾的 word2 相等时需要删除元素的最小次数 dp[i][j]dp[i][j] 应该与以 i2i - 2 字符结尾的 word1 和 以 j2j - 2 字符结尾的 word2 相等时需要删除元素的最小次数 dp[i1][j1]dp[i - 1][j - 1] 相等,即,

dp[i][j]=dp[i1][j1]dp[i][j] = dp[i - 1][j - 1]

word1[i1]!=word2[j1]word1[i - 1] != word2[j - 1] 时,

  • 如果仅删除 word1[i1]word1[i - 1],以 i1i - 1 字符结尾的 word1 和 以 j1j - 1 字符结尾的 word2 相等时需要删除元素的最小次数 dp[i][j]dp[i][j],应该比以 i2i - 2 字符结尾的 word1 和 以 j1j - 1 字符结尾的 word2 相等时需要删除元素的最小次数 dp[i1][j]dp[i - 1][j]11,即 dp[i][j]=dp[i1][j]+1dp[i][j] = dp[i - 1][j] + 1

  • 如果仅删除 word2[j1]word2[j - 1],以 i1i - 1 字符结尾的 word1 和 以 j1j - 1 字符结尾的 word2 相等时需要删除元素的最小次数 dp[i][j]dp[i][j],应该比以 i1i - 1 字符结尾的 word1 和 以 j2j - 2 字符结尾的 word2 相等时需要删除元素的最小次数 dp[i][j1]dp[i][j - 1]11,即 dp[i][j]=dp[i][j1]+1dp[i][j] = dp[i][j - 1] + 1

  • 如果 word1[i1]word1[i - 1]word2[j1]word2[j - 1] 都删除,以 i1i - 1 字符结尾的 word1 和 以 j1j - 1 字符结尾的 word2 相等时需要删除元素的最小次数 dp[i][j]dp[i][j],应该比以 i2i - 2 字符结尾的 word1 和 以 j2j - 2 字符结尾的 word2 相等时需要删除元素的最小次数 dp[i][j1]dp[i][j - 1]22(要删除两次嘛),即 dp[i][j]=dp[i1][j1]+2dp[i][j] = dp[i - 1][j - 1] + 2

综上所述,当 word1[i1]!=word2[j1]word1[i - 1] != word2[j - 1] 时,

dp[i][j]=min(dp[i1][j]+1,dp[i][j1]+1,dp[i1][j1]+2)dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 2)

3、确定 dp 初始状态

  1. dpdp 初始化规模为 (len1+1)×(len2+1)(len_1 + 1) \times (len_2 + 1),且各个元素为 00

  2. dp[i][0]dp[i][0] 为第 00 列哨兵节点,代表 word2 是空字符串时,以 i1i - 1 结尾的 word1 要与 word2 相等,删除元素的最小次数,即 dp[i][0]=idp[i][0] = i

  3. dp[0][j]dp[0][j] 为第 00 行哨兵节点,代表 word1 是空字符串时,以 j1j - 1 结尾的 word2 要与 word1 相等,删除元素的最小次数,即 dp[0][j]=idp[0][j] = i;

4、确定遍历顺序

  • 外层循环遍历 word1,从 i=1i=1i=len1i=len_1

  • 内层循环遍历 word2,从 j=1j=1j=len2j=len_2

5、确定最终返回值

返回值即为,dp[len1][len2]dp[len_1][len_2]

6、代码示例

/**
 * 空间复杂度 O(word1.length * word2.length)
 * 时间复杂度 O(word1.length * word2.length)
 */
function minDistance(word1: string, word2: string): number {
    const len1 = word1.length;
    const len2 = word2.length;
    const dp = Array.from({ length: len1 + 1 }, () => new Array(len2 + 1).fill(0));

    for (let i = 0; i <= len1; i++) {
        dp[i][0] = i;
    }

    for (let j = 0; j <= len2; j++) {
        dp[0][j] = j;
    }

    for (let i = 1; i <= len1; i++) {
        for (let j = 1; j <= len2; j++) {
            if (word1[i - 1] === word2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 2);
            }
        }
    }

    return dp[len1][len2];
};

参考

# 重识动态规划