[Week 9] LeetCode 72. Edit Distance

15 篇文章 0 订阅
13 篇文章 0 订阅

LeetCode 72. Edit Distance

问题描述

Given two words word1 and word2, find the minimum number of operations required to convert word1 to word2.

You have the following 3 operations permitted on a word:

  1. Insert a character
  2. Delete a character
  3. Replace a character

示例

Example 1

Input: word1 = "horse", word2 = "ros"
Output: 3
Explanation: 
horse -> rorse (replace 'h' with 'r')
rorse -> rose (remove 'r')
rose -> ros (remove 'e')

Example 2

Input: word1 = "intention", word2 = "execution"
Output: 5
Explanation: 
intention -> inention (remove 't')
inention -> enention (replace 'i' with 'e')
enention -> exention (replace 'n' with 'x')
exention -> exection (replace 'n' with 'c')
exection -> execution (insert 'u')

题解

Edit Distance 是 DP 的一个经典问题了,其求解的是两个不同的字符串如何通过最少的操作来转换。

DP 最重要的是如何设计状态转移方程,我们可以从这个角度入手,修改一个字符串有三个操作:删除、添加或更换字符,这对于我们设计子问题具有启发性!

考虑长度为 m 的字符串 s 和长度为 n 的字符串 t,最简单的想法就是分别从头到尾扫描 s 和 t:(其中 i 为扫描 s 的指针,j 为扫描 t 的指针)

  • i = j,则 i 和 j 同时往右移
  • i != j,则对 s(三选一):
    • 删除 i 所指字符,i 往右移,j 不动
    • 添加 j 所指字符,i 不动,j 往右移
    • 更改 i 所指字符,i 和 j 同时往右移
  • 如果 s 和 t 扫描未完成,继续上面的操作;否则:
    • s 扫描完,在 s 后按序添加 j 后所有字符
    • t 扫描完,删除 i 后所有字符

问题的关键就在 i != j 这一步,三种方式我们应该选择哪一种呢?或许我们可以把问题抽象成 E(i, j) 表示 s[1..i] 转化成 t[1..j] 所需操作的最少步数。如果得到 E(i, j) 这个状态呢?答案是:

E(i, j) = min{E(i-1, j) + 1, E(i, j-1) + 1, E(i-1, j-1) + diff(i, j)}

其中:

  • E(i-1, j) + 1 表示 s[1..i-1]t[1..j] 匹配,即删除 s[i]
  • E(i, j-1) + 1 表示 s[1..i]t[1..j-1] 匹配,即添加 t[j]s[i]
  • E(i-1, j-1) + diff(i, j) 表示 s[1..i-1]t[1..j-1] 匹配,即根据 s[i]t[j] 异同决定修改

为什么它能成功?

观察一下状态转移方程 E(i, j) = min{E(i-1, j) + 1, E(i, j-1) + 1, E(i-1, j-1) + diff(i, j)},其依赖的数据都是比它更小的问题,说明我们只需要从小问题算起,最后 E(m, n) 就是我们的解!

废话不多少,直接上代码。

Code

class Solution {
public:
  int minDistance(string word1, string word2)
  {
    int subProblemTable[word1.size() + 1][word2.size() + 1];

    // word1[1..i] -> "" needs at least i times operation
    for (size_t i = 0; i <= word1.size(); ++i)
      subProblemTable[i][0] = i;

    // word2[1..i] -> "" needs at least i times operation
    for (size_t i = 0; i <= word2.size(); ++i)
      subProblemTable[0][i] = i;

    for (size_t i = 1; i <= word1.size(); ++i)
    {
      for (size_t j = 1; j <= word2.size(); ++j)
      {
        int min = subProblemTable[i][j - 1] + 1;

        if (min > (subProblemTable[i - 1][j] + 1))
          min = subProblemTable[i - 1][j] + 1;

        int diff = word1[i - 1] == word2[j - 1] ? 0 : 1;
        if (min > (subProblemTable[i - 1][j - 1] + diff))
          min = subProblemTable[i - 1][j - 1] + diff;

        subProblemTable[i][j] = min;
      }
    }

    return subProblemTable[word1.size()][word2.size()];
  }
};

如果你到这里还是没明白,不妨把 subProblemTable 打印一下:

# word1 = "intention"
# word2 = "execution"

. . e x e c u t i o n
. 0 1 2 3 4 5 6 7 8 9
i 1 1 2 3 4 5 6 6 7 8
n 2 2 2 3 4 5 6 7 7 7
t 3 3 3 3 4 5 5 6 7 8
e 4 3 4 3 4 5 6 6 7 8
n 5 4 4 4 4 5 6 7 7 7
t 6 5 5 5 5 5 5 6 7 8
i 7 6 6 6 6 6 6 5 6 7
o 8 7 7 7 7 7 7 6 5 6
n 9 8 8 8 8 8 8 7 6 5

# Output: 5
# Explanation: 
# intention -> inention (remove 't')
# inention -> enention (replace 'i' with 'e')
# enention -> exention (replace 'n' with 'x')
# exention -> exection (replace 'n' with 'c')
# exection -> execution (insert 'u')

复杂度分析

从代码看到,事实上我们是在逐行填写 subProblemTable,在填写每个单元格用时都是 O(1),因此总的时间复杂度恰好是表格的规则,即 O(mn)

摘自算法概论

每个动态规划都隐含着一个 dag 结构:试想用每个节点表示一个子问题,而每条边表示解决子问题时所需要遵循的先后约束。在编辑距离问题中,dag 中的节点对应于子问题,或者,等价地说,对应于表格中的位置 (i, j)。其边为先后关系的约束,形如 (i-1, j) -> (i, j)(i, j-1) -> (i, j)(i-1, j-1) -> (i, j)。实际上,我们可以更进一步,在边上赋予一定的权值,于是求编辑距离就变成了求 dag 中的最短路径!为了看清这一点,除了令 {(i-1, j-1) -> (i, j): s[i] = t[j]} 中的边的长度长度都为 0 外,我们设其它所有边的长度均为 1。编辑距离的最终答案就是点 s={0, 0}t={m, n} 之间的距离。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值