编辑距离 动态规划

72. 编辑距离

给你两个单词 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。至于删除和插入操作,也是类似:

  删除操作,把字符串1i位置删掉,然后去前移i,去计算dp[i-1][j]

  插入操作,在字符串1i位置后插入一个字符,使与字符串2j位置相同,这样搞定了字符串2j位置,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]我们也有用处。然后我初始化二维数组的方式也是非常常见的用法,一定要掌握。

  写到这个博客时我突然悟出了一个道理,人对算法学习就是这么奇怪哈,有时候能永远理解道理,却不能永远记忆用法和实现细节;有时候不能永远理解道理,却能永远记住写法。不管怎么说,选择合适自己的,最终能把题做出来就好~

posted @   JunanP  阅读(11)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示