【算法】【朴素二维DP + 滚动数组优化】力扣72. 编辑距离

72. 编辑距离


【算法】力扣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 的 word1 与长度为 j 的 word2 所需的最少操作数。

Base Case:

  • dp[0][0] = 0
  • dp[0][j] = j j ∈ [1, m]
  • dp[n][0] = n i ∈ [1, n]
    n是word1的长度,m是word2的长度

状态转移:

word1[i] == word2[j],则不需要进行操作。

对于不相等的字符,需要考虑插入、删除和替换三种操作。

插入操作
  • 插入到 word1 中:insr1 = dp[i - 1][j] + 1

  • 插入到 word2 中:insr2 = dp[i][j - 1] + 1

  • 解释: 对于插入,如果对word1进行插入,在长度为i - 1word1和长度为jword2的状态下,即dp[i - 1][j],因为插入而转移到dp[i][j];对word2进行插入也是同理,即dp[i][j - 1]转移到dp[i][j]

删除操作
  • 删除 word1 的字符:dele1 = dp[i - 1][j] + 1
  • 删除 word2 的字符:dele2 = dp[i][j - 1] + 1
  • 解释: 与上文的插入操作类似,如果要删除word1的第i个字符,那么就相当于不需要匹配word1的第i个字符,此时可以从长度为 i - 1word1的状态转移过来。
替换操作
  • 替换操作:repl = dp[i - 1][j - 1] + 1

  • 解释: 既然word1[i]word2[j]被替换成一样的字符,那么就相当于在dp[i - 1][j - 1]这个状态中往word1word2两边增加一个相同的字符使得word1的长度变为iword2的长度变为j

最终,dp[i][j] 的值为这些操作中的最小值。

PS:你可能注意到了有些状态转移方程是一样的,但是这里为了直观易理解,在朴素DP的代码中,不进一步抽象简化。

朴素DP

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        n, m = len(word1), len(word2)
        dp = [[0] * (m + 1) for _ in range(n + 1)]
        
        for i in range(1, n + 1):
            dp[i][0] = i
        for i in range(1, m + 1):
            dp[0][i] = i
        
        for i in range(1, n + 1):
            for j in range(1, m + 1):
                if word1[i - 1] != word2[j - 1]:
                    insr1 = dp[i - 1][j] + 1
                    insr2 = dp[i][j - 1] + 1
                    dele1 = dp[i - 1][j] + 1
                    dele2 = dp[i][j - 1] + 1
                    repl = dp[i - 1][j - 1] + 1
                    dp[i][j] = min(insr1, insr2, dele1, dele2, repl)
                else:
                    dp[i][j] = dp[i - 1][j - 1]
        
        return dp[n][m]

复杂度分析

  • 时间复杂度: O ( n ∗ m ) O(n*m) O(nm),其中 n 和 m 分别为 word1 和 word2 的长度。
  • 空间复杂度: O ( n ∗ m ) O(n*m) O(nm),需要一个二维数组来存储状态信息。

滚动数组优化DP

对于每一个dp[i],我们实际上至多只会用两次,例如,当i = 1的时候,会用到dp[1][j - 1],即dp[i][j - 1],当i = 2的时候,会用到dp[1][j],即dp[i - 1][j]

那么,我们可以用滚动数组的思路,把dp表优化成一个内嵌两个数组的二维数组。

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        n, m = len(word1), len(word2)
        dp = [[0] * (m + 1) for _ in range(2)]
        for j in range(1, m + 1):
            dp[0][j] = j

        for i in range(1, n + 1):
            dp[0][0] = i - 1
            dp[1][0] = i
            for j in range(1, m + 1):
                if word1[i - 1] != word2[j - 1]:

                    insr1_dele1 = dp[0][j] + 1
                    insr2_dele2 = dp[1][j - 1] + 1
                    repl  = dp[0][j - 1] + 1

                    dp[1][j] = min(insr2_dele2, insr1_dele1, repl)
                else:
                    dp[1][j] = dp[0][j - 1]
            # 更新滚动数组
            dp[0] = dp[1]
            dp[1] = [0] * (m + 1)

        return dp[0][m]

步骤解析

  1. 初始化滚动数组: 首先,我们初始化一个长度为2的滚动数组 dp,其中 dp[0]dp[1] 分别代表两个相邻的行。
dp = [[0] * (m + 1) for _ in range(2)]
  1. 初始化第一行: 对于第一行,我们按照基本的编辑距离规则进行初始化,即 dp[0][j] = j,表示从空字符串到 word2 的编辑距离。
for j in range(1, m + 1):
    dp[0][j] = j
  1. 滚动数组核心逻辑: 在计算编辑距离的过程中,我们使用两行来交替存储当前行和上一行的状态信息。通过滚动数组,我们可以优化空间复杂度。
for i in range(1, n + 1):
	# 在原本的朴素解法中,dp[i][0] = i 的base case
    dp[0][0] = i - 1  
    dp[1][0] = i
    for j in range(1, m + 1):
        if word1[i - 1] != word2[j - 1]:
            insr1_dele1 = dp[0][j] + 1
            insr2_dele2 = dp[1][j - 1] + 1
            repl  = dp[0][j - 1] + 1
            dp[1][j] = min(insr2_dele2, insr1_dele1, repl)
        else:
            dp[1][j] = dp[0][j - 1]
    # 滚动数组
    dp[0] = dp[1]
    dp[1] = [0] * (m + 1)
  1. 返回结果: 最终,编辑距离的结果存储在 dp[0][m] 中,即最后一行的最后一个元素。

复杂度分析

  • 时间复杂度: O ( n ∗ m ) O(n*m) O(nm),其中 n 和 m 分别为 word1 和 word2 的长度。
  • 空间复杂度: O ( 2 ∗ m ) O(2*m) O(2m),仅仅保存上一层的结果,不需要保存整个dp表。

总结

通过动态规划的思想,我们成功地解决了编辑距离问题。通过分析状态转移方程,我们能够清晰地理解每一步的操作是如何影响最终结果的。在代码实现中,我们使用了二维数组 dp 来存储状态,通过填表的方式计算最终的编辑距离。同时,我们也介绍了使用滚动数组的优化方案,大大减少了空间复杂度,更高效地解决了问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值