题目描述
给了两个单词word1和word2,问如果将word1转化为word2需要最少操作数?
可以怎么操作?插入字符,删除字符,替换字符
f1-动态规划-自底向上 |
基本分析
- 先明确可能的操作有几种?6种
- 以上6种有没有等价的,可以减少思考维度?有等价的,最后剩3种,word1增加,word1删除,word1修改
- 怎么定义状态?比较常见,定义为f[i][j]表示word1的前i个字符修改为word2的前j个字符的最少,因为考虑有0,0的情况,比字符长度多定义一位
- 考虑怎么转移?(1)分析第i位个第j位的关系,相同则可用f[i-1][j-1]转移;(2)不相同则在3个操作(删、增加、改)中取最小
- 对某个词是0的情况怎么进行边界处理?区分word1和word2是0的情况。(1)word1是0,锁定f[i]是0, 需要在列的维度上逐个字符去加;(2)word2是0,锁定f[j]是0,需要在行的维度上逐个去加
代码
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
f = [[0]*(n+1) for _ in range(m+1)]
# 预处理都是0的情况
f[0][0] = 0
# word2是0的情况,对应word1删除
for i in range(1, m+1):
f[i][0] = f[i-1][0] + 1
# word1是0的情况,对应word1增加
for j in range(1, n+1):
f[0][j] = f[0][j-1] + 1
# 其他情况,取3种可能最小
for i in range(1, m+1):
for j in range(1, n+1):
if word1[i-1] == word2[j-1]:
f[i][j] = f[i-1][j-1]
else:
f[i][j] = min(f[i-1][j-1], f[i][j-1], f[i-1][j]) + 1
return f[-1][-1]
复杂度
时间:\(O(m \cdot n)\)
空间:\(O(m \cdot n\)
总结
涉及这种2个字符串匹配的,考虑最少是两个维度,看有没有可能前i去满足前j
这里操作比较多,需要考虑有没有可能等价,这里把6转化成了3
这里的操作针对的是前i个前j,删除、增加、修改并没有限定位置
在转移的时候,根据末尾做了分类,相同简单,不同是3个的最小+1
定义状态的时候考虑要不要多定义一位?前0位在dp里面是有意义的,所以多定义了一位,在写末尾判断的时候,需要注意索引是第几位-1
对首行和首列的边界条件,可以先预处理了,这样一般情况的dp转移不用考虑太多边界
f2-状态压缩+自顶向下 |
基本分析
其他同上,这里考虑怎么用记忆化搜索实现
- 和dp的区别?(1)两个的方向不一样,dp是00时候知道结果,搜索是终点知道结果(某一个达到长度);(2)传递时候已知的方向不一样,一个前一个后
- 怎么写搜索?(1)参数:处理到的索引,从0开始;(2)结束情况:某一个单词达到了长度,剩余就能口算了,特别的,这里可以考虑边界上的情况;(2)转移:某位等和不等两种情况;(3)返回:最少次数
代码
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
@cache
def helper(i, j):
# 达到了word1的长度:
if i == m:
return n-j
# 达到了word2的长度
if j == n:
return m-i
if word1[i] == word2[j]:
return helper(i+1, j+1)
else:
ins = helper(i+1, j) + 1
dele = helper(i, j+1) + 1
rep = helper(i+1, j+1) + 1
return min(ins, dele, rep)
return helper(0, 0)
复杂度
时间:\(O(m \cdot n)\)
空间:\(O(m \cdot n)\)
总结
需要注意两者方向上的差别,导致在已知状态+递归方向上的区别
另外判定索引和单词长度的写法,避免了额外边界的讨论