leetcode 72编辑距离

LeetCode 72. 编辑距离(动态规划)

一、完整代码(精简注释版)

复制代码
class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.length(), n = word2.length();

        // dp[i][j]:word1前i个字符转换成word2前j个字符的最少操作次数
        vector<vector<int>> dp(m+1, vector<int>(n+1, 0));

        // 初始化:word2为空,需要删除word1的所有字符
        for(int i=0; i<=m; i++){
            dp[i][0] = i;
        }

        // 初始化:word1为空,需要插入word2的所有字符
        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]+1,     // 删除
                        dp[i][j-1]+1,     // 插入
                        dp[i-1][j-1]+1   // 替换
                    });
                }
            }
        }

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

二、动态规划五步总结

1. 确定 dp 数组的含义

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

例如:

复制代码
word1 = "horse"
word2 = "ros"

dp[1][1]:将 "h" 转换成 "r" 的最少操作次数
dp[2][1]:将 "ho" 转换成 "r" 的最少操作次数
dp[5][3]:将 "horse" 转换成 "ros" 的最少操作次数

注意:DP 数组下标表示字符数量,所以实际比较字符时需要使用 word1[i-1] 和 word2[j-1]。

2. 确定状态转移方程(重点)

分为两种情况。

情况一:当前两个字符相同

复制代码
if(word1[i-1] == word2[j-1]){
    dp[i][j] = dp[i-1][j-1];
}

因为当前字符相同,不需要额外操作,直接继承左上角的状态。

情况二:当前两个字符不同

可以选择删除、插入、替换三种操作:

复制代码
dp[i][j] = min({
    dp[i-1][j]+1,     // 删除
    dp[i][j-1]+1,     // 插入
    dp[i-1][j-1]+1   // 替换
});

具体理解如下:

状态转移 操作 含义
dp[i-1][j]+1 删除 删除 word1 当前字符
dp[i][j-1]+1 插入 在 word1 中插入 word2 当前字符
dp[i-1][j-1]+1 替换 将 word1 当前字符替换成 word2 当前字符

其中 +1 表示当前执行一次编辑操作。

min() 表示三种方案只选择操作次数最少的一种,而不是执行全部三种操作。

3. 初始化 dp 数组

复制代码
for(int i=0; i<=m; i++){
    dp[i][0] = i;
}

for(int j=0; j<=n; j++){
    dp[0][j] = j;
}
  • dp[i][0]=i:word2 为空,需要将 word1 的前 i 个字符全部删除。

  • dp[0][j]=j:word1 为空,需要插入 j 个字符。

  • dp[0][0]=0:两个字符串都为空,不需要操作。

4. 确定遍历顺序

复制代码
for(int i=1; i<=m; i++){
    for(int j=1; j<=n; j++){
        // 状态转移
    }
}

因为当前状态 dp[i][j] 依赖三个已经计算的状态:

复制代码
dp[i-1][j-1]   左上方
dp[i-1][j]     上方
dp[i][j-1]     左方

因此从上到下、从左到右遍历即可。

5. 返回最终结果

复制代码
return dp[m][n];

因为 m、n 分别表示两个字符串的完整长度,所以 dp[m][n] 就是将整个 word1 转换为 word2 的最少操作次数。

三、举例模拟

复制代码
word1 = "ab"
word2 = "ac"

初始化并计算完成后:

空 a c
空 0 1 2
a 1 0 1
b 2 1 1

计算 dp[2][2]:

由于 b != c,因此:

复制代码
dp[2][2] = min({
    dp[1][2]+1,    // 2+1=3,删除
    dp[2][1]+1,    // 1+1=2,插入
    dp[1][1]+1     // 0+1=1,替换
});

最终:

复制代码
dp[2][2] = 1;

因为只需要将 b 替换成 c,就能把 "ab" 转换成 "ac"。

四、复杂度分析

时间复杂度: \(O(mn)\)

两层循环遍历整个二维 DP 数组。

空间复杂度: \(O(mn)\)

需要创建大小为 (m+1) × (n+1) 的二维数组。

五、重点记忆

  1. DP 含义:dp[i][j] 表示两个字符串前缀之间的最少编辑次数。

  2. 字符相同:直接继承左上角 dp[i-1][j-1]。

  3. 字符不同:删除、插入、替换三种操作取最小值。

  4. 初始化:dp[i][0]=i,dp[0][j]=j。

  5. 遍历范围:从 1 开始,使用 <=m 和 <=n,最后返回 dp[m][n]。

核心代码模板:

复制代码
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
    });
}

另外,C++ 中:

复制代码
min(a, b);          // 两个数取最小值
min({a, b, c});     // 三个数取最小值
max({a, b, c});     // 三个数取最大值

一句话总结:编辑距离就是利用二维 DP,当前字符相同则不增加操作次数,不同则从删除、插入、替换三种方案中选择代价最小的方案。

相关推荐
神工坊1 小时前
技术分享︱SIMFORGE HSF® 网格加密与热点加速技术实践
算法·hpc·网格·cae·工程仿真·数值引擎·samr
Nil2081 小时前
leetcode 136只出现一次的数字
leetcode·哈希算法·散列表
CV@CV2 小时前
世界模型与智能体协同推理逻辑
人工智能·算法·边缘计算·量子计算·具身智能
jiayoushijie-泽宣2 小时前
SHIFT-PathFormer:专家轨迹蒸馏、连续安全优化与超大迷宫实验
算法
猿究院_夏燊2 小时前
SpringMVC 到底是怎么干活的?
java·网络·职场和发展
UIU1143 小时前
P1003 [NOIP 2011 提高组] 铺地毯
数据结构·c++·算法
爱读书的小胖3 小时前
LeetCode 88. 合并两个有序数组 C 解题分析
c语言·数据结构·算法
员工风险治理专家黄海波3 小时前
商业利益冲突预防性内控与职责分离如何减少主观偏差
职场和发展