编辑距离(Edit Distance)

📌 定义

编辑距离(也称Levenshtein距离)是指两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。允许的编辑操作包括:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

核心思路

使用动态规划求解,定义状态:

  • dp[i][j] 表示将 word1[0...i-1] 转换为 word2[0...j-1] 所需的最少操作次数
word1 = "horse"
word2 = "ros"

转换过程:
horse → rorse (替换h为r)
rorse → rose  (删除r)
rose  → ros   (删除e)

编辑距离 = 3

💡 状态转移方程

设 dp[i][j] 表示 word1 的前 i 个字符转换成 word2 的前 j 个字符所需的最少操作次数。

边界是空字符串:

  • dp[i][0] = i:只能连续删除 i 个字符。
  • dp[0][j] = j:只能连续插入 j 个字符。

当 word1[i-1] == word2[j-1] 时,最后一个字符不需要操作:

dp[i][j] = dp[i-1][j-1]

否则最后一步只有三种选择:

最后一步转移含义
删除 word1[i-1]dp[i-1][j] + 1word1 少看一个字符
插入 word2[j-1]dp[i][j-1] + 1先让 word1 多出一个目标字符
替换 word1[i-1]dp[i-1][j-1] + 1两边各少看一个字符

因此:

dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])

为什么这样不会漏解

任意一次最优转换的最后一步,必然属于删除、插入、替换或无需操作四种情况。枚举最后一步,再从更短的前缀转移,就把所有可能的最优方案覆盖了。

复杂度分析

指标复杂度说明
时间复杂度O(m×n)m和n分别是两个字符串的长度
空间复杂度O(m×n)需要二维DP数组
优化空间O(min(m,n))滚动数组优化

Go 代码

Go 实现

package main
 
import "fmt"
 
func minDistance(word1, word2 string) int {
    m, n := len(word1), len(word2)
 
    if m == 0 {
        return n
    }
    if n == 0 {
        return m
    }
 
    dp := make([][]int, m+1)
    for i := range dp {
        dp[i] = make([]int, n+1)
    }
 
    // 初始化
    for i := 0; i <= m; i++ {
        dp[i][0] = i
    }
    for j := 0; j <= n; j++ {
        dp[0][j] = j
    }
 
    // 填充DP表
    for i := 1; i <= m; i++ {
        for j := 1; j <= n; j++ {
            if word1[i-1] == word2[j-1] {
                dp[i][j] = dp[i-1][j-1]
            } else {
                dp[i][j] = min(
                    dp[i-1][j]+1,   // 删除
                    dp[i][j-1]+1,   // 插入
                    dp[i-1][j-1]+1, // 替换
                )
            }
        }
    }
 
    return dp[m][n]
}
 
func min(a, b, c int) int {
    result := a
    if b < result {
        result = b
    }
    if c < result {
        result = c
    }
    return result
}
 
func main() {
    word1 := "horse"
    word2 := "ros"
 
    fmt.Printf("'%s' → '%s' 的编辑距离: %d\n",
        word1, word2, minDistance(word1, word2))
}

🎯 DP表格示例

word1 = "horse"
word2 = "ros"

    ""  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

dp[5][3] = 3 (答案)

经典题目

基础应用

变体

⚖️ 优缺点

优点

  • ✅ 经典DP问题:理解动态规划的好例子
  • ✅ 应用广泛:拼写纠错、文本相似度等
  • ✅ 可扩展:可以自定义操作代价

缺点

  • ❌ 时间复杂度O(mn):大字符串计算慢
  • ❌ 空间复杂度高:虽然可以优化到O(n)

🎨 应用场景

  1. 拼写纠错:查找最相似的正确单词
  2. DNA序列比对:计算基因序列相似度
  3. 文本相似度:计算两个文档的相似程度
  4. diff工具:文件版本对比
  5. 模糊搜索:容错搜索

💡 编辑距离的变体

1. 带权编辑距离

如果插入、删除、替换的代价不同,只需要把转移中的 +1 替换成对应代价,状态定义和遍历顺序不变。

2. 最长公共子序列(LCS)

编辑距离的特殊情况(只允许插入和删除):

如果只允许插入和删除,设两个字符串长度分别为 m、n,则:

distance = m + n - 2 × LCS(word1, word2)

3. 滚动数组优化

当前行只依赖上一行和当前行左侧的值,因此可以把二维数组压缩成一维数组,空间降为 O(min(m, n))。压缩时要额外保存左上角的旧值,避免覆盖 dp[i-1][j-1]。

相关主题


返回:字符串算法 | 算法学习导航