编辑距离是用于比较两个字符串或者符号序列之间距离的常用距离函数。
含义为:给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数,可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
用 leetcode 的题目来解释:
示例 1:
输入: word1 = “horse”, word2 = “ros”
输出: 3
解释:
horse -> rorse (将 'h’替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)
示例 2:
输入: word1 = “intention”, word2 = “execution”
输出: 5
解释:
intention -> inention (删除 ‘t’)
inention -> enention (将 ‘i’ 替换为 ‘e’)
enention ->exention (将 ‘n’ 替换为 ‘x’)
exention -> exection (将 ‘n’ 替换为 ‘c’)
exection -> execution (插入 ‘u’)
动态规划计算编辑距离
动态规划的核心思想是用将原问题拆解成子问题
考虑:word1 = ‘ecf’,word2 = ‘abcd’
不放假设 word2 = ‘abcd’ 为固定模板,对 word1 = ‘ecf’ 进行编辑操作。
从后往前匹配(从前向后匹配也是可以的),先比较最后一个字符:‘f’ ≠ \neq =‘d’,那么就要对 word1 进行编辑,允许的编辑操作有三种:替换、插入、删除。
-
替换
替换 f f f 成 d d d,即 e c f → e c d ecf \rightarrow ecd ecf→ecd,接下来就比较 e c d ecd ecd 和 a b c d abcd abcd 的编辑距离,则有:
E D ( e c f , a b c d ) = E D ( e c d , a b c d ) + 1 = E D ( e c , a b c ) + 1 ED(ecf,abcd) = ED(ecd,abcd) + 1 = ED(ec,abc) + 1 ED(ecf,abcd)=ED(ecd,abcd)+1=ED(ec,abc)+1 -
插入
在 word1 中插入 d d d,即 e c f → e c f d ecf \rightarrow ecfd ecf→ecfd,接下来就比较 e c f d ecfd ecfd 和 a b c d abcd abcd 的编辑距离,则有:
E D ( e c f , a b c d ) = E D ( e c f d , a b c d ) + 1 = E D ( e c f , a b c ) + 1 ED(ecf,abcd) = ED(ecfd,abcd) + 1 = ED(ecf,abc) + 1 ED(ecf,abcd)=ED(ecfd,abcd)+1=ED(ecf,abc)+1 -
删除
删除 word1 中不匹配的字符 f f f,即 e c f → e c ecf \rightarrow ec ecf→ec,接下来就比较 e c ec ec 和 a b c d abcd abcd 的编辑距离,则有:
E D ( e c f , a b c d ) = E D ( e c , a b c d ) + 1 ED(ecf,abcd) = ED(ec,abcd) + 1 ED(ecf,abcd)=ED(ec,abcd)+1
综上所述,
E
D
(
e
c
f
,
a
b
c
d
)
=
min
{
E
D
(
e
c
,
a
b
c
)
,
E
D
(
e
c
f
,
a
b
c
)
,
E
D
(
e
c
,
a
b
c
d
)
}
+
1
ED(ecf,abcd) = \min\{ ED(ec,abc) ,ED(ecf,abc), ED(ec,abcd) \}+ 1
ED(ecf,abcd)=min{ED(ec,abc),ED(ecf,abc),ED(ec,abcd)}+1
如果我们已知
- E D ( e c f , a b c ) = 3 ED(ecf,abc) = 3 ED(ecf,abc)=3
- E D ( e c , a b c d ) = 3 ED(ec,abcd) = 3 ED(ec,abcd)=3
- E D ( e c , a b c ) = 2 ED(ec,abc) = 2 ED(ec,abc)=2
那么: E D ( e c f , a b c d ) = m i n ( 3 , 3 , 2 ) + 1 = 3 ED(ecf,abcd) = min(3,3,2) + 1 = 3 ED(ecf,abcd)=min(3,3,2)+1=3
可以看出,原问题拆解成了三个规模更小子问题,那么就可以使用动态规划来求解了!
def editDistance(word1: str, word2: str) -> int:
n1 = len(word1)
n2 = len(word2)
dp = [[0] * (n2 + 1) for _ in range(n1 + 1)]
# init
for j in range(1, n2 + 1):
dp[0][j] = j
for i in range(1, n1 + 1):
dp[i][0] = i
for i in range(1, n1 + 1):
for j in range(1, n2 + 1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1] ) + 1
return dp[-1][-1]
用一个 4 × 5 4\times 5 4×5 的数组 dp 来记录动态规划的过程:
- 初始化
\, | ‘’ | a | b | c | d |
---|---|---|---|---|---|
‘’ | 0 | 1 | 2 | 3 | 4 |
e | 1 | - | - | - | - |
c | 2 | - | - | - | - |
f | 3 | - | - | - | - |
- 结果
\, | ‘’ | a | b | c | d |
---|---|---|---|---|---|
‘’ | 0 | 1 | 2 | 3 | 4 |
e | 1 | 1 | 2 | 3 | 4 |
c | 2 | 2 | 2 | 2 | 3 |
f | 3 | 3 | 3 | 3 | 3 |