题目描述
给定两个单词 word1 和 word2 ,请问最少需要进行多少次操作可以将 word1 变成 word2 。
你可以进行三种操作:
- 插入一个字符;
- 删除一个字符;
- 修改一个字符;
样例1
输入: word1 = "horse", word2 = "ros"
输出: 3
解释:
horse -> rorse (用 'r' 替换 'h')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
样例2
输入: word1 = "intention", word2 = "execution"
输出: 5
解释:
intention -> inention (删除 't')
inention -> enention (用 'e' 替换 'i')
enention -> exention (用 'x' 替换 'n')
exention -> exection (用 'c' 替换 'n')
exection -> execution (插入 'u')
算法
(动态规划) $O(n^2)$
经典的编辑距离问题。
状态表示:$f[i, j]$ 表示将 word1 的前 $i$ 个字符变成 word2 的前 $j$ 个字符,最少需要进行多少次操作。
状态转移,一共有四种情况(假定word的下标从1开始):
- 将 $word1[i]$ 删除或在 $word2[j]$ 后面添加 $word1[i]$,则其操作次数等于 $f[i-1, j] + 1$;
- 将 $word2[j]$ 删除或在 $word1[i]$ 后面添加 $word2[j]$,则其操作次数等于 $f[i, j-1] + 1$;
- 如果 $word1[i] = word2[j]$,则其操作次数等于 $f[i-1, j-1]$;
- 如果 $word1[i] \neq word2[j]$,则其操作次数等于 $f[i-1, j-1] + 1$;
时间复杂度分析:状态数 $O(n^2)$,状态转移复杂度是 $O(1)$,所以总时间复杂度是 $O(n^2)$。
C++ 代码
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.size(), m = word2.size();
if (!n || !m) return n + m;
vector<vector<int>>f =
vector<vector<int>>(n + 1,
vector<int>(m + 1));
f[0][0] = 0;
for (int i = 1; i <= n; i ++ ) f[i][0] = i;
for (int j = 1; j <= m; j ++ ) f[0][j] = j;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
f[i][j] = f[i - 1][j - 1] +
(word1[i - 1] != word2[j - 1]);
f[i][j] = min(f[i][j], f[i - 1][j] + 1);
f[i][j] = min(f[i][j], f[i][j - 1] + 1);
}
return f[n][m];
}
};
好难理解直接在 word1[i] 后面添加 word2[j],则其操作次数等于 f[i,j−1]+1;反过来将 word2[j] 删除倒是很好想明白。如果反过来想,就和情况1是一样的,但是正着想,不应该是前i-1个字符和前j-1个字符一样,然后插入第i个,正好等于第j个(虽然这样看,又很像3,4里的replace的情况),如果前i个和前j-1个一样,那么插入以后就是f[i+1, j]了呀,请大佬解惑一下
注意这里的
i
和j
表示的是原字符串的下标,而不是新字符串的下标,所以在i
后插入的字符的下标不是i + 1
。f[i,j]—将word1前i个字符变成word2前j个字符,最少需要多少次操作
状态转移
如果word1[i]≠word2[j],
采取修改最后一个字符的办法,有f[i-1,j-1]+1
采取word1[i]删除,有f[i-1,j]+1(即把word1[i]删除后,将word1[1…i-1]转换为word2[1…j])
采取word1[i]后添加word2[j],有f[i,j-1]+1(在word1后添加一个字符,相当于在word2中减少一个字符。即把word1[1…i]转换为word2[1…j-1]后,添加word2[j])
为什么
f[i][j] = f[i - 1][j - 1] + (word1[i - 1] != word2[j - 1]);
放在f[i][j] = min(f[i][j], f[i - 1][j] + 1); f[i][j] = min(f[i][j], f[i][j - 1] + 1);
后面就 a不了呢.. 不是说 增/删/替换 的顺序 不会影响 最终的答案吗..可以调整顺序,但要改成
f[i][j] = min(f[i][j], f[i - 1][j - 1] + (word1[i - 1] != word2[j - 1]))
,而不能直接赋值。在字符相等的时候直接让f[i][j] = f[i - 1][j - 1]也能通过,怎么证明相等时 f[i - 1][j] + 1和 f[i][j - 1] + 1一定会大于等于 f[i - 1][j - 1]呢?
所以直接把三种情况都判断一遍,就不需要证明了呀,而且即使去掉这种情况,也不会降低代码的时间复杂度,为啥要给自己添麻烦嘛。如果确实想证明,其实也不复杂,你可以假定最优解不是s[i]匹配p[j],然后逐个分析每种情况:删掉s[i]、在s后添加一个字符、删掉p[j]、在p后面添加一个字符,会发现最优解不管在哪一类里,都可以转化到s[i]匹配p[j]这一类,而且操作步数不会增加,从而可以证明s[i]匹配p[j]这一类里一定有最优解。以最优解最后一步是删掉s[i]为例,那么说明p[j]是和s[i]之前的某个字符匹配,比如是s[k],k<i,那么现在我们不删掉s[i],改成删掉s[k],然后令p[j]去匹配s[i]即可,这样并没有增加操作步数,但是把方案转换到s[i]匹配s[j]了。
前i个字符,最后一位的索引应该是i-1,题解的思路分析的好像都换成word1[i-1],word2[j-1]更准确一点,有点乱,不知道是不是这样的。
多谢指正,分析前加上了一个补充:假定word的下标从1开始。
word2[j]后面添加 word1[i],则其操作次数等于 f[i−1,j] + 1 咋理解呢?
在
word2[j]
后面添加word1[i]
之后word1[1...i]
和word2[1...j]
就匹配了,说明在添加之前word1[1...i-1]
需要和word2[1...j]
匹配,这一步的操作次数最小是f[i - 1, j] + 1
懂了,谢谢y大~
最大是f[i - 1, j] + 1?
本题问的是最小操作次数,不是最大值。