(LeetCodeHot100)72. 编辑距离——edit-distance
(LeetCodeHot100)72. 编辑距离——edit-distance
zhangzhang72. 编辑距离——edit-distance
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 1:
1 | 输入:word1 = "horse", word2 = "ros" |
示例 2:
1 | 输入:word1 = "intention", word2 = "execution" |
提示:
0 <= word1.length, word2.length <= 500word1和word2由小写英文字母组成
- 没看出来咋写,看看官方解答:
想法
编辑距离算法被数据科学家广泛应用,是用作机器翻译和语音识别评价标准的基本算法。
最直观的方法是暴力检查所有可能的编辑方法,取最短的一个。所有可能的编辑方法达到指数级,但我们不需要进行这么多计算,因为我们只需要找到距离最短的序列而不是所有可能的序列。
方法一:动态规划
思路和算法
我们可以对任意一个单词进行三种操作:
- 插入一个字符;
- 删除一个字符;
- 替换一个字符。
题目给定了两个单词,设为 A 和 B,这样我们就能够六种操作方法。
但我们可以发现,如果我们有单词 A 和单词 B:
- 对单词
A删除一个字符和对单词B插入一个字符是等价的。例如当单词A为doge,单词B为dog时,我们既可以删除单词A的最后一个字符e,得到相同的dog,也可以在单词B末尾添加一个字符e,得到相同的doge; - 同理,对单词
B删除一个字符和对单词A插入一个字符也是等价的; - 对单词
A替换一个字符和对单词B替换一个字符是等价的。例如当单词A为bat,单词B为cat时,我们修改单词A的第一个字母b -> c,和修改单词B的第一个字母c -> b是等价的。
这样以来,本质不同的操作实际上只有三种:
- 在单词
A中插入一个字符; - 在单词
B中插入一个字符; - 修改单词
A的一个字符。
这样以来,我们就可以把原问题转化为规模较小的子问题。我们用 A = horse,B = ros 作为例子,来看一看是如何把这个问题转化为规模较小的若干子问题的。
- 在单词
A中插入一个字符:如果我们知道horse到ro的编辑距离为a,那么显然horse到ros的编辑距离不会超过a + 1。这是因为我们可以在a次操作后将horse和ro变为相同的字符串,只需要额外的1次操作,在单词A的末尾添加字符s,就能在a + 1次操作后将horse和ro变为相同的字符串; - 在单词
B中插入一个字符:如果我们知道hors到ros的编辑距离为b,那么显然horse到ros的编辑距离不会超过b + 1,原因同上; - 修改单词
A的一个字符:如果我们知道hors到ro的编辑距离为c,那么显然horse到ros的编辑距离不会超过c + 1,原因同上。
那么从 horse 变成 ros 的编辑距离应该为 min(a + 1, b + 1, c + 1)。
注意:为什么我们总是在单词 A 和 B 的末尾插入或者修改字符,能不能在其它的地方进行操作呢?答案是可以的,但是我们知道,操作的顺序是不影响最终的结果的。例如对于单词 cat,我们希望在 c 和 a 之间添加字符 d 并且将字符 t 修改为字符 b,那么这两个操作无论为什么顺序,都会得到最终的结果 cdab。
你可能觉得 horse 到 ro 这个问题也很难解决。但是没关系,我们可以继续用上面的方法拆分这个问题,对于这个问题拆分出来的所有子问题,我们也可以继续拆分,直到:
- 字符串
A为空,如从转换到ro,显然编辑距离为字符串B的长度,这里是2; - 字符串
B为空,如从horse转换到,显然编辑距离为字符串A的长度,这里是5。
因此,我们就可以使用动态规划来解决这个问题了。我们用 D[i][j] 表示 A 的前 i 个字母和 B 的前 j 个字母之间的编辑距离。
如上所述,当我们获得 D[i][j-1],D[i-1][j] 和 D[i-1][j-1] 的值之后就可以计算出 D[i][j]。
D[i][j-1]为A的前i个字符和B的前j - 1个字符编辑距离的子问题。即对于B的第j个字符,我们在A的末尾添加了一个相同的字符,那么D[i][j]最小可以为D[i][j-1] + 1;D[i-1][j]为A的前i - 1个字符和B的前j个字符编辑距离的子问题。即对于A的第i个字符,我们在B的末尾添加了一个相同的字符,那么D[i][j]最小可以为D[i-1][j] + 1;D[i-1][j-1]为A前i - 1个字符和B的前j - 1个字符编辑距离的子问题。即对于B的第j个字符,我们修改A的第i个字符使它们相同,那么D[i][j]最小可以为D[i-1][j-1] + 1。特别地,如果A的第i个字符和B的第j个字符原本就相同,那么我们实际上不需要进行修改操作。在这种情况下,D[i][j]最小可以为D[i-1][j-1]。
那么我们可以写出如下的状态转移方程:
若
A和B的最后一个字母相同:1
D[i][j] = min(D[i][j−1] + 1, D[i−1][j] + 1, D[i−1][j−1])
若
A和B的最后一个字母不同:1
D[i][j] = 1 + min(D[i][j−1], D[i−1][j], D[i−1][j−1] − 1)
所以每一步结果都将基于上一步的计算结果,示意如下:
对于边界情况,一个空串和一个非空串的编辑距离为 D[i][0] = i 和 D[0][j] = j,D[i][0] 相当于对 word1 执行 i 次删除操作,D[0][j] 相当于对 word1执行 j 次插入操作。
综上我们得到了算法的全部流程。
答案
1 | class Solution { |
复杂度分析
- 时间复杂度 :O(mn),其中 m 为
word1的长度,n 为word2的长度。 - 空间复杂度 :O(mn),我们需要大小为 O(mn) 的 D 数组来记录状态值。
这里那三种操作有点没解释清楚:
清楚版本:
- 此题聚焦于要想知道某个时刻的状态,得知道前面的状态,而且可以知道最初状态,所以再做这道题的时候要想起那个表格(代码中left和down相当于进行了插入或者删除,而left_down是左下角的数,看是否相同,下面有详细解释)
假设我们有两个字符串 word1 和 word2,我们的目标是将 word1 转换成 word2。设 dp[i][j] 表示的是将 word1 的前 i 个字符转换成 word2 的前 j 个字符所需的最小编辑操作数。
情况1: 字符相等
如果 word1 的第 i 个字符(即 word1[i-1])等于 word2 的第 j 个字符(即 word2[j-1]),那么这两个字符之间不需要任何编辑操作,因此 dp[i][j] = dp[i-1][j-1]。
情况2: 字符不等
如果 word1[i-1] != word2[j-1],那么我们需要考虑三种编辑操作:
替换:我们可以将 word1[i-1] 替换为 word2[j-1],这样两个字符串的最后一个字符就相同了,然后问题就转化为了将 word1 的前 i-1 个字符转换成 word2 的前 j-1 个字符,所以需要的操作数是 dp[i-1][j-1] + 1。
插入:在 word1 中插入 word2[j-1],这相当于将 word1 的前 i 个字符转换成 word2 的前 j-1 个字符后,再插入一个字符,因此需要的操作数是 dp[i][j-1] + 1。(考虑前i个字符转换为j-1,再额外插入第j个字符)
删除:从 word1 中删除 word1[i-1],这就把问题转化为将 word1 的前 i-1 个字符转换成 word2 的前 j 个字符,所需的操作数是 dp[i-1][j] + 1。(不用考虑删除的第i个字符,即前i-1个字符转换为前j个字符)
最终,dp[i][j] 应该取这三种情况中的最小值,因为我们要找的是最少编辑操作次数。





