写给对问题有了解的人
从一个例子开始(Leetcode72)
a = 'horse', b= 'row'
从a变成b最短需要几步?
输出: 3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
从上面的解释我们可以知道什么?
看第一步,将 'h' 替换为 'r'。
- 是否可以直接删除h呢?显然可以。
- 是否可以在h前面插入r呢?当然也行。
- 将h替换成r呢?也是可以的。
选择很多,哪个选择更好呢?分别使用三种方法做一次改变,然后递归下去。
比较三种方法需要的最小次数就行了
作为动态规划的经典题目,先写递归再转移方程
递归
以下陈述的前提是将a变成b
- 如果a[i] == b[i]。如
a = 'one'
,b='ohe'
,计算a='ne'
和b=‘he’
的编辑距离就行. - 如果a[i] != b[i],比较三种方法需要的最短编辑距离就行
- 插入法,插入后可保证a[i] == b[i],此时需要计算
a[i:]
和b[i+1:]
的编辑距离。如a='abc'
,b='dbc'
,先将a变成'dabc'
,然后比较a='abc'
与b='bc'
的编辑距离。当然具体编程的时候不需要真的插入元素,只需要将结果加上1就行了 - 删除法。老规矩,
a='abc'
,b='dbc'
,将a变成bc
,然后比较a='bc'
和b=‘dbc’
的编辑距离 - 替换法。
a='abc'
,b='dbc'
。将a变成'dbc'
,比较a='bc’和b='bc’的编辑距离即可
- 插入法,插入后可保证a[i] == b[i],此时需要计算
综上所述,递归方程为
f
(
a
,
b
)
=
{
f
(
a
[
i
+
1
:
]
,
b
[
i
+
1
:
]
)
a[i]=b[i]
m
i
n
(
f
(
a
,
b
[
i
+
1
:
]
)
,
f
(
a
[
i
+
1
:
]
,
b
)
,
f
(
a
[
i
+
1
:
]
,
b
[
i
+
1
:
]
)
)
+
1
a[i]!=b[i]
l
e
n
(
a
)
len(b)==0
l
e
n
(
b
)
len(a)==0
f(a, b) = \begin{cases} f(a[i+1:], b[i+1:])& \text{a[i]=b[i]}\\ min( f( a, b[i+1:]), f(a[i+1:], b), f(a[i+1:], b[i+1:]) )+1& \text{a[i]!=b[i]}\\ len(a)& \text{len(b)==0}\\ len(b)& \text{len(a)==0} \end{cases}
f(a,b)=⎩⎪⎪⎪⎨⎪⎪⎪⎧f(a[i+1:],b[i+1:])min(f(a,b[i+1:]),f(a[i+1:],b),f(a[i+1:],b[i+1:]))+1len(a)len(b)a[i]=b[i]a[i]!=b[i]len(b)==0len(a)==0
Python代码
def editdistance(a, b):
if not a: # 条件四
if not b:
return 0
return len(b)
if not b: # 条件三
if not a:
return 0
return len(a)
for i in range(len(b)):
if a[i] == b[i]: # 条件一
return editdistance(a[i+1:], b[i+1:])
else: # 条件二
# 删除、插入、替换
delete = 1 + editdistance(a[i+1:], b[i:])
insert = 1 + editdistance(a[i:], b[i+1:])
replace = 1 + editdistance(a[i+1:], b[i+1:])
return min(delete, insert, replace)
但是递归的时间复杂度是让人难以忍受的
动态规划
有了递归,动态规划的代码就好写了
附上一个LeetCode72自己已经AC的代码
class Solution:
def minDistance(self, a: str, b: str) -> int:
if not a:
if not b:
return 0
return len(b)
if not b:
if not a:
return 0
return len(a)
row, col = len(a), len(b)
DP = [[0 for _ in range(col+1)] for _ in range(row+1)]
for i in range(1, col+1):
DP[0][i] = i
for i in range(1, row+1):
DP[i][0] = i
for i in range(1, row+1):
for j in range(1, col+1):
if a[i-1] == b[j-1]:
DP[i][j] = DP[i-1][j-1]
else:
DP[i][j] = min(DP[i-1][j-1], DP[i][j-1], DP[i-1][j]) + 1
return DP[-1][-1]
代码很简单,把
DP[i][j] = min(DP[i-1][j-1], DP[i][j-1], DP[i-1][j]) + 1
拿出来说一下,这个对应的相当于条件二