深入剖析编辑距离-动态规划解法

动态规划解编辑距离问题:公式解析与操作含义

编辑距离(Edit Distance)是一个经典的动态规划问题,广泛应用于字符串相似度分析、拼写纠正等领域。它的目标是计算将字符串 A A A 转换为字符串 B B B 的最少操作次数,允许的操作包括插入删除替换。在本文中,我们不仅会推导编辑距离的动态规划公式,还将深入解释公式如何映射到具体操作。


1. 问题定义

什么是编辑距离?

编辑距离是指将字符串 A A A 转换为字符串 B B B 的最小操作次数。假设字符串 A A A 的长度为 m m m,字符串 B B B 的长度为 n n n,允许以下操作:

  1. 插入 :在 A A A 中插入一个字符。
  2. 删除 :从 A A A 中删除一个字符。
  3. 替换 :将 A A A 的一个字符替换为另一个字符。

2. 动态规划解法

动态规划定义

我们定义 d p i j dpij dpij 为将字符串 A 1 ... i A1 \\dots i A1...i 转换为 B 1 ... j B1 \\dots j B1...j 的最小操作次数。基于问题的定义,可以递归地推导出状态转移公式。

初始条件

  1. i = 0 i = 0 i=0:
    A A A 是空字符串时,需要插入 j j j 个字符以匹配 B 1 ... j B1 \\dots j B1...j,因此:
    d p 0 j = j dp0j = j dp0j=j
  2. j = 0 j = 0 j=0:
    B B B 是空字符串时,需要删除 i i i 个字符以匹配 A 1 ... i A1 \\dots i A1...i,因此:
    d p i 0 = i dpi0 = i dpi0=i
  3. i = 0 i = 0 i=0 且 j = 0 j = 0 j=0:
    两个空字符串之间的编辑距离显然是 0:
    d p 0 0 = 0 dp00 = 0 dp00=0

状态转移公式

我们分两种情况讨论:

  1. A i = B j Ai = Bj Ai=Bj

    如果当前字符相同,则无需额外操作,问题可以递归为子问题:

    d p i j = d p i − 1 j − 1 dpij = dpi-1j-1 dpij=dpi−1j−1

  2. A i ≠ B j Ai \neq Bj Ai=Bj

    如果当前字符不同,我们需要选择以下三种操作之一,并选择代价最小的路径:

    • 删除操作 :删除 A i Ai Ai,对应转化为子问题 d p i − 1 j + 1 dpi-1j + 1 dpi−1j+1;
    • 插入操作 :在 A A A 中插入一个字符,使其匹配 B j Bj Bj,对应子问题 d p i j − 1 + 1 dpij-1 + 1 dpij−1+1;
    • 替换操作 :将 A i Ai Ai 替换为 B j Bj Bj,对应子问题 d p i − 1 j − 1 + 1 dpi-1j-1 + 1 dpi−1j−1+1。

综合上述情况,公式为:
d p i j = { d p i − 1 j − 1 , if A i = B j 1 + min ⁡ ( d p i − 1 j , d p i j − 1 , d p i − 1 j − 1 ) , if A i ≠ B j dpij = \begin{cases} dpi-1j-1, & \text{if } Ai = Bj \\ 1 + \min(dpi-1j, dpij-1, dpi-1j-1), & \text{if } Ai \neq Bj \end{cases} dpij={dpi−1j−1,1+min(dpi−1j,dpij−1,dpi−1j−1),if Ai=Bjif Ai=Bj


3. 动态规划公式中的操作解释(这是理解递推公式的重点!!!)

删除操作: d p i − 1 j dpi-1j dpi−1j

  • 操作含义 :从 A 1 ... i A1 \\dots i A1...i 转换到 B 1 ... j B1 \\dots j B1...j 时,选择删除 A i Ai Ai
  • 剩余问题 :此时只需将 A 1 ... ( i − 1 ) A1 \\dots (i-1) A1...(i−1) 转换为 B 1 ... j B1 \\dots j B1...j
  • 成本 :删除一个字符的代价是 1,因此:
    d p i j = d p i − 1 j + 1 dpij = dpi-1j + 1 dpij=dpi−1j+1

插入操作: d p i j − 1 dpij-1 dpij−1

  • 操作含义 :从 A 1 ... i A1 \\dots i A1...i 转换到 B 1 ... j B1 \\dots j B1...j 时,选择在 A A A 中插入一个字符,使其匹配 B j Bj Bj
  • 剩余问题 :此时只需将 A 1 ... i A1 \\dots i A1...i 转换为 B 1 ... ( j − 1 ) B1 \\dots (j-1) B1...(j−1)
  • 成本 :插入一个字符的代价是 1,因此:
    d p i j = d p i j − 1 + 1 dpij = dpij-1 + 1 dpij=dpij−1+1

替换操作: d p i − 1 j − 1 dpi-1j-1 dpi−1j−1

  • 操作含义 :从 A 1 ... i A1 \\dots i A1...i 转换到 B 1 ... j B1 \\dots j B1...j 时,选择将 A i Ai Ai 替换为 B j Bj Bj
  • 剩余问题 :此时只需将 A 1 ... ( i − 1 ) A1 \\dots (i-1) A1...(i−1) 转换为 B 1 ... ( j − 1 ) B1 \\dots (j-1) B1...(j−1)
  • 成本 :替换一个字符的代价是 1,因此:
    d p i j = d p i − 1 j − 1 + 1 dpij = dpi-1j-1 + 1 dpij=dpi−1j−1+1
  • 特殊情况 :如果 A i = B j Ai = Bj Ai=Bj,则无需替换,直接继承之前的状态:
    d p i j = d p i − 1 j − 1 dpij = dpi-1j-1 dpij=dpi−1j−1

4. 示例解析

问题描述

我们以将 A = " h o r s e " A = "horse" A="horse" 转换为 B = " r o s " B = "ros" B="ros" 为例,求解编辑距离。

动态规划表构建

按照上述公式,构建 d p dp dp 表如下:

"" r o s
"" 0 1 2 3
h 1 1 2 3
o 2 2 1 2
r 3 2 2 2
s 4 3 3 2
e 5 4 4 3

结果解释

表格右下角的值 d p 5 3 = 3 dp53 = 3 dp53=3 表示从 "horse" 转换为 "ros" 的最小操作次数为 3。

操作路径

通过回溯路径,可以得出操作序列:

  1. 删除 h h h:"horse" → "orse";
  2. 替换 o o o 为 r r r:"orse" → "rrse";
  3. 删除 e e e:"rrse" → "ros"。

python3 代码实现

python 复制代码
def min_edit_distance(A: str, B: str) -> int:
    """
    计算将字符串 A 转换为字符串 B 的最小编辑距离。
    动态规划实现,时间复杂度 O(m * n),空间复杂度 O(m * n)。

    :param A: 源字符串
    :param B: 目标字符串
    :return: 最小编辑距离
    """
    m, n = len(A), len(B)

    # 初始化 dp 表
    dp = [[0] * (n + 1) for _ in range(m + 1)]

    # 填充第一行和第一列
    for i in range(m + 1):
        dp[i][0] = i  # 转换为空字符串所需的删除操作
    for j in range(n + 1):
        dp[0][j] = j  # 从空字符串转化为目标字符串所需的插入操作

    # 填充 dp 表
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if A[i - 1] == B[j - 1]:  # 字符匹配,无需操作
                dp[i][j] = dp[i - 1][j - 1]
            else:  # 插入、删除、替换操作中取最小值
                dp[i][j] = 1 + min(
                    dp[i - 1][j],    # 删除
                    dp[i][j - 1],    # 插入
                    dp[i - 1][j - 1] # 替换
                )

    # 返回右下角的结果
    return dp[m][n]


# 示例
A = "horse"
B = "ros"
result = min_edit_distance(A, B)
print(f"将字符串 '{A}' 转换为 '{B}' 的最小编辑距离是: {result}")

5. 总结

动态规划解决编辑距离问题的核心是通过子问题递归,将问题分解为最小操作步骤。我们使用 d p i j dpij dpij 存储每一步的最优解,通过状态转移公式明确地映射到三种基本操作(插入、删除、替换)。理解公式背后的操作含义,不仅有助于解决具体问题,还能加深对动态规划本质的理解。

希望这篇文章能帮助你掌握编辑距离问题的解法与原理!如有疑问或需要进一步的示例分析,欢迎留言讨论!

相关推荐
人道领域5 小时前
【LeetCode刷题日记】47.全排列Ⅱ
java·开发语言·算法·leetcode
Navigator_Z5 小时前
LeetCode //C - 1095. Find in Mountain Array
c语言·算法·leetcode
暖阳华笺10 小时前
【数据结构与算法】哈希专题
数据结构·c++·算法·leetcode·哈希算法
AKA__Zas11 小时前
芝士算法(滑动窗口片 2.0)
java·算法·leetcode·学习方法
四代水门11 小时前
LeetCode刷算法题(C++)
c++·算法·leetcode
退休倒计时1 天前
【每日一题】LeetCode 53. 最大子数组和 TypeScript
数据结构·算法·leetcode·typescript
洛水水1 天前
【力扣100题】86.柱状图中最大的矩形
算法·leetcode·职场和发展
洛水水1 天前
【力扣100题】81.寻找两个正序数组的中位数
数据结构·算法·leetcode
洛水水1 天前
【力扣100题】85.每日温度
算法·leetcode·职场和发展
Kurisu_红莉栖1 天前
力扣56合并区间
算法·leetcode