当前位置: 首页 > article >正文

LeetCode 72.编辑距离

要解决LeetCode 72题“编辑距离”问题,我们可以使用动态规划的方法。以下是详细的C++解题思路和实现过程:

解题思路

  1. 问题分析
    编辑距离问题要求将字符串 word1 转换为 word2 所需的最少操作次数,允许的操作包括插入、删除和替换字符。动态规划适用于这类具有重叠子问题和最优子结构的问题。

  2. 状态定义
    定义二维数组 dp[i][j],表示将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最小操作次数。

  3. 状态转移方程

    • 字符匹配:若 word1[i-1] == word2[j-1],则无需操作,直接继承前一步的结果:
      dp[i][j] = dp[i-1][j-1]
    • 字符不匹配:需从插入、删除、替换中选择操作次数最少的一种:
      dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
  4. 初始化

    • dp[i][0] = i:删除 i 个字符使 word1 变空。
    • dp[0][j] = j:插入 j 个字符使空字符串变为 word2 的前 j 个字符。
  5. 填充顺序
    按顺序遍历 i1mj1n,确保子问题已解决。

三种操作的具体解释

你提到的三种操作对应的状态转移方程理解是正确的,但可能还不够直观。让我们通过具体场景表格推导来彻底理解这些操作的数学含义,以及为什么最终要取这三个值的最小值。


1、操作的本质与状态转移关系

假设我们有两个字符串:

  • word1 = "abc"(长度 m=3)
  • word2 = "abd"(长度 n=3)

建立二维DP表 dp[4][4](索引从0到3):

i\j0 (空)1 (a)2 (b)3 (d)
00123
1 (a)1???
2 (b)2???
3 ©3???

现在我们要计算 dp[3][3](即 word1[0..2]转成word2[0..2])。


场景1:插入操作(对应 dp[i][j-1] + 1
  • 当前字符word1[2] = 'c' vs word2[2] = 'd'
  • 插入操作:在word1末尾插入d,此时word1变为"abcd"
    • 此时word1[3]d已经匹配word2[2]d
    • 剩下的问题转化为将word1[0..2](即"abc")转为word2[0..1](即"ab"
    • 这个子问题的解是 dp[3][2]
    • 总操作次数 = dp[3][2] + 1

场景2:删除操作(对应 dp[i-1][j] + 1
  • 删除操作:删除word1[2]c,此时word1变为"ab"
    • 剩下的问题是将"ab"转为"abd"
    • 这个子问题的解是 dp[2][3]
    • 总操作次数 = dp[2][3] + 1

场景3:替换操作(对应 dp[i-1][j-1] + 1
  • 替换操作:将word1[2]c替换为d
    • 此时word1变为"abd"
    • 剩下的问题是将"ab"转为"ab"
    • 这个子问题的解是 dp[2][2]
    • 总操作次数 = dp[2][2] + 1

2、具体数值推导

假设我们已经计算出前面的值:

i\j0123
00123
11012
22101
3321?

现在计算 dp[3][3]

  1. 插入dp[3][2] + 1 = 1 + 1 = 2
  2. 删除dp[2][3] + 1 = 1 + 1 = 2
  3. 替换dp[2][2] + 1 = 0 + 1 = 1

最终取最小值 1,即通过替换操作只需1步。


3、为什么取三种操作的最小值?

这三种操作涵盖了所有可能的编辑方式:

  1. 插入:适用于word1word2短的情况(需要补字符)
  2. 删除:适用于word1word2长的情况(需要删冗余字符)
  3. 替换:适用于字符不匹配但长度相同的情况

通过动态规划,我们始终选择历史最优解(已计算的子问题最优解)加上当前操作的最小代价。


4、边界条件的深层理解
  • dp[i][0] = i:将长度为i的字符串变为空串,只能删除i次
  • dp[0][j] = j:将空串变为长度为j的字符串,只能插入j次

例如:

  • word1 = "abc", word2 = "" → 需要删除3次
  • word1 = "", word2 = "abc" → 需要插入3次

C++代码实现

#include <vector>
#include <algorithm> // 用于min函数

using namespace std;

int minDistance(string word1, string word2) {
    int m = word1.size(), 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;
    
    // 填充dp数组
    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];
}

代码解析

  • 时间复杂度:O(m*n),需要遍历二维数组的所有元素。
  • 空间复杂度:O(m*n),用于存储动态规划表。
  • 关键点
    • 通过比较当前字符是否相等决定操作方式。
    • 使用 min({a, b, c}) 取三者最小值(C++11特性)。
    • 边界条件初始化正确处理空字符串的情况。

示例验证

  • 输入word1 = "horse", word2 = "ros"
    输出:3
    解释:替换 h→r,删除 r,删除 e,共3步。

  • 输入word1 = "intention", word2 = "execution"
    输出:5
    解释:通过替换、删除等操作匹配字符串。


http://www.kler.cn/a/535077.html

相关文章:

  • 【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-Chapter2-HTML 中的 JavaScript
  • Spring容器初始化扩展点:ApplicationContextInitializer
  • 禅道社区版项目管理软件部署(记录篇)
  • B站自研的第二代视频连麦系统(上)
  • GitHub 使用教程:从入门到进阶
  • 《Kettle保姆级教学-界面介绍》
  • UE5 蓝图学习计划 - Day 14:搭建基础游戏场景
  • MySQL InnoDB引擎 高度为3的B+树,可以存储的数据量
  • 高级java每日一道面试题-2025年01月30日-框架篇[SpringBoot篇]-如何理解 Spring Boot 配置加载顺序 ?
  • 树欲静而凤不止
  • redis之RDB持久化过程
  • Spring Boot整合MQTT
  • 2025游戏行业的趋势预测
  • GB/T 43698-2024 《网络安全技术 软件供应链安全要求》标准解读
  • Docker镜像管理:掌握save/load与export/import的精髓
  • 90.子集||
  • python学opencv|读取图像(五十五)使用cv2.medianBlur()函数实现图像像素中值滤波处理
  • node.js使用mysql2对接数据库
  • 【分布式理论五】分布式调用(3):服务注册与发现
  • Python批量重命名文件的实用案例
  • 【Linux高级IO】五种IO模型
  • 手写MVVM框架-渲染v-for列表(修改List)
  • VUE 集成企微机器人通知
  • hot100(8)
  • 《工业4.0时代?!》
  • 【Flutter】【WEB3】判断一个String是不是钱包地址