【leetcode-Python】-Dynamic Programming-72. Edit Distance

题目链接

https://leetcode.com/problems/edit-distance/

题目描述

给定两个单词word1和word2,计算由word1转化为word2需要的最少操作数。

可以对一个单词进行以下三种操作:(1)插入一个字符;(2)删除一个字符;(3)替换一个字符。

示例

输入:word1=“horse”,word2="ros"

输出:3

horse->rorse(将'h'替换为'r')

rorse->rose(删除'r')

rose->ros(删除'e')

解题思路

利用自底向上的动态规划算法来求解。

dp[i][j]表示word1的前i个元素(word1[0],...,word1[i-1])转化为word2的前j个元素(word2[0],...,word2[j-1])需要的最少操作数。base case为i=0或者j=0时的情况,当i=0时,dp[0][j]表示一个空字符串转化为word2的前j个元素所需要的最少操作数,因此有dp[0][j] = j,也就是要进行j次插入字符操作;当j=0时,dp[i][0]表示word1的前i个元素转化为空字符串所需要的最少操作数,因此有dp[i][0] = i,即要进行i次删除字符操作。

接下来进行将dp[i][j]的取值问题分解为子问题:

如果word1[i-1] == word2[j-1],word1的第i个元素和word2的第j个元素相等(由于需要操作数最少,那么这里就不应该做操作),此时dp[i][j]就等于word1前i-1个元素转化为word2前j-1个元素所需要的最少操作数,即dp[i-1][j-1]。

如果word1[i-1] != word2[j-1],由于在更新dp[i][j]之前dp[i-1][j]、dp[i][j-1]、dp[i-1][j-1]都已经更新完了,即对应的最少操作数都计算出来了,当前可选的选择为:

(1)进行字符替换,将word1[i-1]换为word2[j-1],此时dp[i][j] = dp[i-1][j-1] + 1,即word1前i-1个元素(word1[0],...,word1[i-2])转化为word2前j-1个元素(word2[0],...,word2[j-2])所需要的最少操作数+当前替换操作。

(2)进行字符删除,将word1[i-1]删除,此时dp[i][j] = dp[i-1][j] + 1,即word1前i-1个元素(word1[0],...,word1[i-2])转化为word2前j个元素所需要的最少操作数+当前删除操作。

(3)进行字符插入,在word1[i-1]后面插入一个和word2[j]相等的元素,此时dp[i][j] = dp[i][j-1]+1,即word1前i-1个元素转化为word2前j-2个元素所需要的最少操作数+当前插入操作。

由于题目中要求的是最少操作数,因此要取dp[i][j]可能取值中的最小值来更新dp[i][j]。

对于示例中的word1=“horse”,word2="ros",更新结束后dp二维数组为:

再举个例子:

令word1="abc",word2="defgh",当前已知从"ab"到"def"之间的最小编辑距离,接下来计算从"abc"到"defg"之间的最小编辑距离,i = 3,j = 4:

由于'c'!= 'g',那么有三种选择:

(1)字符替换,将'c'替换成'g',dp[i][j] = dp[i-1][j-1] + 1。

(2)字符删除,如果已知"ab"和“defg”之间的最小编辑距离,可以将‘c’删除。dp[i][j] = dp[i-1][j] + 1。

(3)字符插入,如果已知“abc”和"def"之间的最小编辑距离,可以在'c'后面插入'g'。此时dp[i][j] = dp[i][j-1]+1。

Python实现

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        m,n = len(word1),len(word2)
        dp = [[0 for _ in range(n+1)] for _ in range(m+1)] #这里要注意,行数写外面,列数写里面
        for j in range(0,n+1):
            dp[0][j] = j
        for i in range(0,m+1):
            dp[i][0] = i
        for i in range(1,m+1):
            for j in range(1,n+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],dp[i][j-1],dp[i-1][j-1])+1 
        return dp[m][n]
                

        

二维dp数组一般尺寸为(m+1) * (n+1),因为可以让dp[i][0]、dp[0][j]表示空字符串的情况。

时间复杂度与空间复杂度

时间复杂度为O(mn),空间复杂度为O(mn)。

状态压缩

由于dp[i][j]的更新仅与dp[i-1][j-1](左上角)、dp[i-1][j](左边)、dp[i][j-1](上边)有关,因此空间复杂度可以进一步减小,将二维数组投射到一维数组上。压缩时一般是将第一个维度i去掉,只保留j。(在行数大于列数的情况下,如果行数小于列数,则需要调用函数并改变参数位置。由word1转化为word2需要的最少操作数等于由word2转化为word1所需要的最少操作数)这个一维数组只能表示二维数组中的某一行dp[i][...],遍历i时,一维数组也随之刷新(外层循环i,内层循环j)。

因此当前需要关注的是dp[i-1][j-1]、dp[i-1][j]、dp[i][j-1]这些变量如何存储。在更新当前行时,未更新的dp[j]可以表示dp[i-1][j],更新后的dp[j-1]可以表示dp[i][j-1]。dp[i-1][j-1]为还没更新的dp[j-1],因此在内层循环更新dp[j]之前,先将dp[j]赋给tmp,更新完dp[j]后再将tmp赋值给prerow_precol,即当内层迭代到j+1时,prerow_precol存储的就是上次迭代中更新前的dp[j]了。

Python实现

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        m,n = len(word1),len(word2)
        if m < n: #认为word1长度大于word2
            return self.minDistance(word2,word1)
        dp = [i for i in range(n+1)] #base case
        for i in range(1,m+1):
            prerow_precol = dp[0]
            dp[0] = i
            for j in range(1,n+1):
                tmp = dp[j]#没更新前的dp[j]对应二维数组中的dp[i-1][j]
                if(word1[i-1] == word2[j-1]):
                    dp[j] = prerow_precol 
                else:
                    dp[j] = min(dp[j-1],prerow_precol,dp[j]) + 1
                #更新prerow_precol
                prerow_precol = tmp #将dp[i-1][j]保存到下一次循环中,就对应dp[i][j+1]的左上角元素
        return dp[-1]
                
            

        

时间复杂度与空间复杂度

时间复杂度为O(mn),空间复杂度为O(min(m,n))

参考

https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/dong-tai-gui-hua-ji-ben-ji-qiao/zhuang-tai-ya-suo-ji-qiao

https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/dong-tai-gui-hua-ji-ben-ji-qiao/zhuang-tai-ya-suo-ji-qiao

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值