编辑距离 动态规划
给你两个单词 word1
和 word2
,请你计算出将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 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')
思路:
编辑距离是经典的用二维动态规划解决的问题!不要想着用你自己的智商和方式去解决这个问题,这样只会很痛苦和怀疑自己;知道是动态规划的套路之后,记住解决方法即可!
定义一个动态规划数组dp[][],dp[i][j]表示只考虑第一个字符串的前i位和第二的字符串的前j位,要进行几次操作。为了方便理解,我们从下标为1开始,可知最终结果就是要求dp[lenth1][lenth2]。
首先我们要定义base case,如果dp[i][j],i或j有任何一方是0,相当于我们把一个空字符串变成一个非空字符串(dp[0][j]),只能是不断添加,添加j位;或者把一个非空字符串变成一个空字符串(dp[i][0]),只能是不断删除,删除i位。即当出现空字符串时,编辑距离是对方的长度。
对于其他情况,如果你想一步到位。就请直接记住这个非常好记的状态转移方程:
dp[i][j]=min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
然后直接跑到下面去看代码,把一些细节看清,这篇文章就可以结束了。下面的可以不看了。
-----------------------------------------------分割线---------------------------------------------------------------
好的,接下来是对详细思路的一个碎碎念。除去base case之后,当我们的i指针和j指针在两个字符串上游走时:如果这两个所处位置字符相同,则直接dp[i][j]=dp[i-1][j-1],因为这两个是一样的,不需要做任何“编辑”;如果字符不同,则就来到了我们的dp[i][j]=min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1,最后面的+1是很好理解的,因为既然这两个字符不同,我们就必然要进行一次编辑操作,即操作数+1。但关键是,我们怎么知道要做哪一种操作呢?我们有“删除”,“插入”和“替换”三种选择,其实就对应着代码里min()当中的三种写法,因为我们不知道哪种操作的操作数最少,所以加上min()。
比如说对于“替换”的操作,我们需要把字符串1当中的第i位变成和字符串2当中的第j位,进行了一次操作后,我们就可以前移i和j,继续去对比dp[i-1][j-1],所以“替换”操作下,dp[i][j]就等于dp[i-1][j-1]+1。至于删除和插入操作,也是类似:
删除操作,把字符串1的i位置删掉,然后去前移i,去计算dp[i-1][j]
插入操作,在字符串1的i位置后插入一个字符,使与字符串2的j位置相同,这样搞定了字符串2的j位置,j前移,继续去计算dp[i][j-1]
可以结合图去理解(这里再次推荐《labuladong的算法小抄》,上面有很清楚的图片解释)。
代码:
class Solution(object):
def minDistance(self, word1, word2):
len1 = len(word1)
len2 = len(word2)
dp=[[0]*(len2+1) for _ in range(len1+1)]#初始化二维数组
#定义base case 如果一个是0,则修编辑距离直接等于对方的长度
for i in range(1,len1+1):
dp[i][0]=i
for j in range(1,len2+1):
dp[0][j]=j
#开始用正式动态规划
for i in range(1,len1+1):
for j in range(1,len2+1):
if word1[i-1]==word2[j-1]:#如果字符相同
dp[i][j]=dp[i-1][j-1]#跳过 不操作
else:#若字符不同
#下面分别对应删除 插入 替换操作,都是操作 都要+1
#因为我们不知道那种操作的操作数最少,取min,别写成max
dp[i][j]=min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1#别忘了操作+1
return dp[len1][len2]
小结:
注意,我们是以下标从1开始定义dp数组的,所以dp数组的长度定义为lenth+1,dp[0]我们也有用处。然后我初始化二维数组的方式也是非常常见的用法,一定要掌握。
写到这个博客时我突然悟出了一个道理,人对算法学习就是这么奇怪哈,有时候能永远理解道理,却不能永远记忆用法和实现细节;有时候不能永远理解道理,却能永远记住写法。不管怎么说,选择合适自己的,最终能把题做出来就好~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了