最小编辑距离
LeetCode 72. 编辑距离
希望把字符串 s1 转化为 s2,一次操作可以选择插入/删除/替换,求完成转换的最少操作次数
s1 = “horse”, s2 = “rose”,返回2(horse -> rorse -> rose)
思路:
我们固定s2,然后解决如何从s1转化为s2的问题(两者反过来其实也一样)
套路:两个字符串的动态规划,一般都需要二维dp数组,i、j分别与两个字符串挂钩
- 如果是“连续”的子串问题,i与 以s1[i]结尾的子串 挂钩
- 若果是“不连续”的子序列问题,i与 s1[0…i]的子序列 挂钩
- 使用
dp[i][j]
表示子串s1[0...i]
到子串s2[0...j]
的最小编辑距离(最少操作次数)
每次只考虑末尾的s1[i]
字符怎么向s2[j]
字符靠拢 - 状态:算法推进中会改变的变量,这里就是指针i、j的位置
选择:对于当前的两个子串,可以在s1的末尾选择插入/替换/删除,也可以选择什么也不做(两个字串的末尾字符相同,则无需操作,直接将i、j指针同时向前移即可) - 状态转移:如果i、j所指向的字符相同,那么不用操作,i、j同时前移,求更短子串的最小编辑距离;
如果i、j所指向的字符不同,那么就要在插入/替换/删除中选择一种最优的操作
插入:所需操作数为dp[i][j-1]+1
(在s1末尾插入s2[j]
,j
指针前移)
删除:所需操作数为dp[i-1][j]+1
(在s1末尾删除s1[i]
,i
指针前移)
替换:所需操作数为dp[i-1][j-1]+1
(将s1[i]
换为s2[j]
,i、j
指针前移)
实现:
注意,实际应该在dp数组左侧和上侧多插入一个空白行,使用dp[i][j]
表示子串s1[0...i-1]
到子串s2[0...j-1]
的最小编辑距离
- 这样一方面是防止越界
- 另一方面是表示了base case:例如,
dp[0][j]
表示从空字符串''
到s2[0...j-1]
的最小编辑距离(显然这个值就是s2[0...j-1]
的长度,即要插入的字符个数)
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
len1, len2 = len(word1), len(word2)
dp = [[0 for _ in range(len2 + 1)] for _ in range(len1 + 1)]
# base case
for i in range(len2 + 1):
dp[0][i] = i
for j in range(len1 + 1):
dp[j][0] = j
for i in range(1, len1 + 1):
for j in range(1, len2 + 1):
# dp[i][j]表示子串s1[0...i-1]到子串s2[0...j-1]的最小编辑距离
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(dp[i - 1][j] + 1, # 删除
dp[i][j - 1] + 1, # 插入
dp[i - 1][j - 1] + 1) # 替换
return dp[len1][len2]