编辑距离(Edit Distance)——从字符串相似度到动态规划经典模型

引言

在自然语言处理(NLP)、搜索引擎纠错、DNA序列比对、拼写检查系统等领域,经常需要衡量两个字符串之间的相似程度。

例如:

复制代码
kitten
sitting

显然两个单词非常接近,但究竟有多接近?

如何用数学方法描述这种"接近程度"?

为了解决这一问题,俄罗斯科学家 Vladimir Levenshtein 于 1965 年提出了著名的 Levenshtein Distance(编辑距离)

编辑距离定义为:

将一个字符串转换为另一个字符串所需要执行的最少编辑操作次数。

允许的操作包括:

  • 插入(Insert)

  • 删除(Delete)

  • 替换(Replace)


问题描述

给定两个字符串:

复制代码
word1
word2

求将 word1 转换为 word2 所需的最少操作次数。

例如:

复制代码
horse → ros

转换过程:

复制代码
horse
↓ replace(h,r)
rorse
↓ delete(r)
rose
↓ delete(e)
ros

答案:

复制代码
3

编辑距离的本质

假设:

复制代码
word1 = horse
word2 = ros

我们关注的是:

复制代码
horse前i个字符
与
ros前j个字符

之间的最优转换方案。

因此可以发现:

一个大问题可以分解为若干个更小的子问题。

这正是动态规划的典型特征。


动态规划状态定义

设:

复制代码
dp[i][j]

表示:

复制代码
word1前i个字符
转换成
word2前j个字符

所需的最少操作次数

例如:

复制代码
dp[3][2]

表示:

复制代码
"hor"
→
"ro"

的最小编辑距离。


状态转移推导

这是整道题最核心的部分。


情况一:字符相同

假设:

复制代码
word1[i-1] == word2[j-1]

例如:

复制代码
abc
abc

最后一个字符已经匹配:

复制代码
c == c

无需额外操作。

因此:

复制代码
dp[i][j] = dp[i-1][j-1]

情况二:字符不同

假设:

复制代码
word1[i-1] != word2[j-1]

例如:

复制代码
horse
ros

最后一个字符不同。

此时有三种选择。


1. 删除

删除 word1 的最后一个字符:

复制代码
horse
↓
hors

问题变成:

复制代码
dp[i-1][j]

再加一次删除操作:

复制代码
dp[i-1][j] + 1

2. 插入

向 word1 末尾插入一个字符:

复制代码
hors
↓
horse

对应:

复制代码
dp[i][j-1]

再执行一次插入:

复制代码
dp[i][j-1] + 1

3. 替换

直接修改最后一个字符:

复制代码
horse
↓
rorse

对应:

复制代码
dp[i-1][j-1]

加一次替换:

复制代码
dp[i-1][j-1] + 1

综合状态转移方程

因此得到:

dpij=\min\left(dpi-1j+1,\ dpij-1+1,\ dpi-1j-1+cost\right)

其中:

复制代码
cost =
0  (字符相同)
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;

初始化分析

动态规划中最容易忽略的部分就是边界条件。


第一列

复制代码
dp[i][0]

表示:

复制代码
word1前i个字符
→
空串

唯一办法:

复制代码
全部删除

因此:

复制代码
dp[i][0] = i;

第一行

复制代码
dp[0][j]

表示:

复制代码
空串
→
word2前j个字符

只能不断插入:

复制代码
dp[0][j] = j;

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

代码实现

复制代码
class Solution {
public:
    int minDistance(string word1, string word2) {

        int m = word1.size();
        int n = word2.size();

        vector<vector<int>> dp(
            m + 1,
            vector<int>(n + 1, 0)
        );

        // 初始化
        for(int i = 0; i <= m; i++)
            dp[i][0] = i;

        for(int j = 0; j <= n; j++)
            dp[0][j] = j;

        // 状态转移
        for(int i = 1; i <= m; i++)
        {
            for(int 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],      // 删除
                            dp[i][j-1],      // 插入
                            dp[i-1][j-1]     // 替换
                        }) + 1;
                }
            }
        }

        return dp[m][n];
    }
};

空间优化

观察状态转移:

复制代码
dp[i][j]

仅依赖于:

复制代码
dp[i-1][j]
dp[i][j-1]
dp[i-1][j-1]

即:

  • 当前行

  • 上一行

因此可以压缩成:

复制代码
O(n)

空间复杂度。

优化后:

复制代码
时间复杂度:O(mn)

空间复杂度:O(n)

编辑距离的现实应用

编辑距离远不止是一道算法题。

它广泛应用于工业系统。


搜索引擎纠错

用户输入:

复制代码
googel

系统自动纠正:

复制代码
google

因为:

复制代码
EditDistance(googel, google)=2

拼写检查器

例如:

  • Microsoft Word

  • VSCode

  • IntelliJ IDEA

都会计算候选词的编辑距离。


DNA序列比对

生物信息学中:

复制代码
ACTGCA
ACTACA

可以通过编辑距离衡量基因序列相似度。


OCR文字识别

识别结果:

复制代码
ChatGP7

真实结果:

复制代码
ChatGPT

编辑距离:

复制代码
1

系统可以自动修正。


动态规划思想总结

编辑距离是动态规划中的经典代表问题,其核心思想可以概括为:

  1. 将字符串问题转化为二维状态空间。

  2. 定义 dp[i][j] 表示两个前缀字符串的最优解。

  3. 根据最后一个字符是否相同进行分类讨论。

  4. 将复杂转换过程归结为:

    • 插入

    • 删除

    • 替换

最终建立最优子结构关系。

从算法体系角度看,编辑距离与:

  • 最长公共子序列(LCS)

  • 最长公共子串

  • 不同的子序列

  • 正则表达式匹配

共同构成了**字符串动态规划(String DP)**的重要基础,其思想在自然语言处理、生物信息学以及搜索推荐系统中均具有广泛应用价值。

相关推荐
c238561 小时前
c/c++中的二叉树进阶
c语言·c++·算法
吴可可1232 小时前
Win7下C#开发AutoCAD首选版本
算法
Halo_tjn2 小时前
反射与设计模式2
java·开发语言·算法
_日拱一卒2 小时前
LeetCode:46全排列
算法·leetcode·职场和发展
剑挑星河月3 小时前
31.下一个排列
java·算法·leetcode
凌波粒3 小时前
LeetCode--98.验证二叉搜索树(二叉树)
算法·leetcode·职场和发展
Misnearch3 小时前
3635. 最早完成陆地和水上游乐设施的时间II
leetcode·贪心·排序
Kurisu5753 小时前
深度拆解:从令牌桶到滑动窗口,高并发系统限流算法的数学本质与边界
java·网络·算法
哈泽尔都3 小时前
运动控制教学——5分钟学会力控算法(阻抗/导纳/力位混合)
c++·python·算法·决策树·贪心算法·机器人·gpu算力