Leetcode 72.编辑距离


题目描述:

给你两个单词 word1word2,请你计算出将 word1 转换成 word2 所使用的最少操作数。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1:

1
2
3
4
5
6
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例 2:

1
2
3
4
5
6
7
8
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

提示:

  • 0 <= word1.length, word2.length <= 500
  • word1word2 由小写英文字母组成

链接:

https://leetcode-cn.com/problems/edit-distance


题目分析

  我们需要做的是将 word1 转换到 word2,而每一步操作都只涉及到 1 个字母的变化,一共有 3 种操作,也即插入、替换、删除。其中插入和删除的操作比较难以处理,可能会陷入死循环,有没有办法解决呢?其实 word1 的删除操作和 word2 的插入操作是等价的。比如 abcd -> abc,既可以看做是左边删除了一个 d,变成 abc = abc,也可以视为右边添加了一个 d,变成 abcd = abcd,操作步数都是 1,同样能让两个单词相同。因此我们的方法有以下三种

  • word1 插入一个字符
  • word2 插入一个字符
  • word1 替换一个字符

  注意到以上的操作方法次序对结果是没有影响的,因此我们可以从左到右逐步对字符进行操作,便可以使用动态规划的方法解决。我们逐步分析两个字符串,并保留操作次数最少的方法。dp[i][j] 的值表示 word1 的前 i 个字符与 word2 的前 j 个字符进行匹配需要的最少步数(也即它们的编辑距离)。而每次我们只在字符串的最后进行操作。我们分析状态转移方程,也即 dp[i][j] 的值如何确定。

  • 如果最后一步操作是 word1 插入字符,也即进行了 dp[i][j-1] 的操作之后,我们将 word2 的第 j 个字符插入到 word1 最后。
  • 如果最后一步操作是 word2 插入字符,也即进行了 dp[i-1][j] 的操作之后,我们将 word1 的第 i 个字符插入到 word2 最后。
  • 如果最后一步操作是 word1 替换字符,也即进行了 dp[i-1][j-1] 的操作之后,我们将 word1 的第 i 个字符替换为 word2 的第 j 个字符(如果这两个字符本来就是相同的,则无需替换)。

  以上的三种操作中,我们总是选取操作次数最小的那个作为 dp[i][j] 的值进行状态转移,最后就能够得到 dp[m][n] 也即 word1 转换为 word2 的最少操作数。而边界情况是什么样的?边界情况是其中某一个字符串为空的时候,操作数就是另一个字符串的字符数。也即 dp[0][j] = jword1 为空时,word1 变为 word2 的操作就是往 word1 插入 j 个字符。同理,dp[i][0] = i
  注意字符串的下标是从 0 开始的,因此 word1 的第 i 个字符是 word1[i-1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.size();
int n = word2.size();
if(m * n == 0) return m + n;

vector<vector<int>> dp(m + 1, vector<int>(n + 1));

for(int i = 0; i <= m; i++){
dp[i][0] = i;
}
for(int j = 1; j <= n; j++){
dp[0][j] = j;
}
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
int ist = dp[i][j-1] + 1;
int del = dp[i-1][j] + 1;
int swp = dp[i-1][j-1] + (word1[i-1] == word2[j-1] ? 0 : 1);
dp[i][j] = min(ist, min(del, swp));
}
}
return dp[m][n];
}
};

  时间复杂度:$O(mn)$,其中 $m、n$ 分别是 word1word2 的长度。我们进行了双层遍历。
  空间复杂度:$O(mn)$,其中 $m、n$ 分别是 word1word2 的长度。也即动态规划所需的数组大小。