news 2026/2/2 19:42:42

LeetCode 72. Edit Distance(编辑距离)动态规划详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 72. Edit Distance(编辑距离)动态规划详解

LeetCode 72. Edit Distance(编辑距离)动态规划详解

编辑距离是经典字符串动态规划问题,也是很多高级题目的基础。题目如下。leetcode

给定两个字符串 word1 和 word2,返回将 word1 转换为 word2 所需的最少操作数。允许的操作有三种:

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

leetcode

示例:leetcode

  • word1 = “horse”, word2 = “ros”,输出 3
  • word1 = “intention”, word2 = “execution”,输出 5

字符串长度范围是 0 ~ 500,只包含小写字母。leetcode

一、核心思路:用前缀定义状态

整个问题的关键在于:不要直接从整串去想,而是用「前缀」来描述状态。

定义状态:

dp[i][j]表示:把 word1 的前 i 个字符(下标 0…i-1)转换成 word2 的前 j 个字符(下标 0…j-1)所需的最少操作数。leetcode+1

这样,最终答案就是:

dp[m][n],其中 m = len(word1), n = len(word2)。leetcode+1

**直观理解:**dp 是一个二维表,行是 word1 前缀长度 0…m,列是 word2 前缀长度 0…n,每个格子是「把某个前缀变成另一个前缀的最小编辑次数」。leetcode+1

二、边界初始化:只有插入或只有删除

当某一边是空串时,只剩下「全插入」或者「全删除」两种情况。leetcode+1

dp[0][0] = 0

空串变空串,不需要任何操作。leetcode

dp[i][0] = i(i >= 1)

把 word1 的前 i 个字符变成空串,只能删掉这 i 个字符,所以是 i。leetcode

dp[0][j] = j(j >= 1)

把空串变成 word2 的前 j 个字符,只能插入 j 个字符,所以是 j。leetcode

这部分在代码里就是第一行和第一列的初始化。leetcode

三、状态转移:三种操作对应的来源状态

现在考虑一般情况:i >= 1 且 j >= 1。当前需要处理的是两个前缀的最后一个字符:

word1[i-1]word2[j-1]。leetcode+1

分两种情况:

1. 字符相等:不需要额外操作

如果word1[i-1] == word2[j-1],说明这两个前缀的最后一个字符已经相同了,不需要再对它们做操作:leetcode+1

直接继承前一个状态:

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

也就是「把前 i-1 个变成前 j-1 个」的代价,完全沿用到前 i 和前 j。leetcode+1

2. 字符不等:删除 / 插入 / 替换

如果word1[i-1] != word2[j-1],需要做一次操作,使得「最后一个字符」能够对齐。这里的难点在于,把「操作」和「来源状态」一一对应地理解清楚。leetcode+1

2.1 删除:来自dp[i-1][j] + 1

**动作:**删除 word1 的最后一个字符word1[i-1]

删除后,word1 的前缀长度从 i 变成 i-1,而 word2 仍然是前 j 个字符。

于是问题变成:「把 word1 的前 i-1 个字符变成 word2 的前 j 个字符」,这就是dp[i-1][j]

再加上这一次删除操作,所以:

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

注意思考顺序是:先解决更小的子问题dp[i-1][j],再通过一次“删除最后一个字符”的操作扩展到dp[i][j]。leetcode+1

2.2 插入:来自dp[i][j-1] + 1

插入比较容易混淆,虽然是往 word1 里插入,但来源状态是dp[i][j-1]

**目标:**让 word1 的前 i 个字符最终变成 word2 的前 j 个字符,并且末尾应为word2[j-1]

假设此时已经处理好了dp[i][j-1],即「把 word1 的前 i 个字符变成 word2 的前 j-1 个字符」。

接下来只需要在 word1 的末尾插入word2[j-1]这个字符,就能匹配上「前 j 个字符」。

所以来源是:

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

这里的直觉是:先对齐 word2 的前 j-1 个字符,再插入最后一个,使目标从长度 j-1 扩展到 j。leetcode+1

2.3 替换:来自dp[i-1][j-1] + 1

**动作:**把word1[i-1]替换成word2[j-1]

替换之前,前面已经有 i-1 和 j-1 个字符,问题是「如何把 word1 的前 i-1 个变成 word2 的前 j-1 个」,即dp[i-1][j-1]

替换本身只处理最后一个字符,让它们变得相同。

因此:

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

思路同样是:先搞定更短的前缀 (i-1, j-1),然后通过一次替换把长度扩展到 (i, j)。leetcode+1

3. 综合不等时的转移方程

word1[i-1] != word2[j-1]时,需要在三种方案里取最小值:leetcode+1

dp[i][j] = min( dp[i-1][j] + 1, // 删除 dp[i][j-1] + 1, // 插入 dp[i-1][j-1] + 1 // 替换 )

配合字符相等时的情况,可以总结成:

  • 如果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 的完整转移。leetcode+1

四、用一个小例子感受表格

比如 word1 = “ab”,word2 = “abc”:

  • 行下标 i = 0…2,列下标 j = 0…3。
  • dp[0][j] = jdp[i][0] = i,先把第一行第一列填出来。
  • 然后按顺序填 (1,1)、(1,2)、(1,3)、(2,1)…,每一格都只依赖「上方、左方、左上方」。
  • 在纸上画出 3×4 的网格,用上面三种操作去解释每个格子,可以很快把 i / j 和 i-1 / j-1 的关系彻底吃透。

五、C 语言实现代码(含内存释放)

下面是一个用二维数组实现的 C 解法,对应上面所有的状态定义与转移:leetcode

#include<stdio.h>#include<stdlib.h>#include<string.h>#defineINT_MAX0x7fffffff#defineMIN(a,b)((a)<(b)?(a):(b))intminDistance(char*word1,char*word2){intword1_len,word2_len,i,j,result;intinsert,delete,replace;int**dp;if(word1==NULL||word2==NULL)return0;word1_len=strlen(word1);word2_len=strlen(word2);dp=(int**)malloc((word1_len+1)*sizeof(int*));for(i=0;i<=word1_len;i++){dp[i]=(int*)malloc((word2_len+1)*sizeof(int));for(j=0;j<=word2_len;j++)dp[i][j]=INT_MAX;}dp[0][0]=0;for(i=1;i<=word1_len;i++)dp[i][0]=i;for(j=1;j<=word2_len;j++)dp[0][j]=j;for(i=1;i<=word1_len;i++){for(j=1;j<=word2_len;j++){if(word1[i-1]==word2[j-1]){dp[i][j]=dp[i-1][j-1];}else{delete=dp[i-1][j]+1;insert=dp[i][j-1]+1;replace=dp[i-1][j-1]+1;dp[i][j]=MIN(delete,MIN(insert,replace));}}}result=dp[word1_len][word2_len];for(i=0;i<=word1_len;i++)free(dp[i]);free(dp);dp=NULL;returnresult;}

二维 dp 使用 malloc 动态分配,并在最后完整 free,避免内存泄漏。leetcode

时间复杂度 O(mn),空间复杂度 O(mn),对于 0 ~ 500 的长度完全可以接受。leetcode+1

六、常见疑问:为什么会出现 i / i-1、j / j-1 混在一起?

原因在于:

  • dp[i][j]的 i、j 是「前缀长度」。
  • 字符访问使用的是数组下标,从 0 开始,所以最后一个字符是word1[i-1]word2[j-1]
  • 转移时,总是从「更短的前缀」(i-1, j)、(i, j-1)、(i-1, j-1) 出发,加 1 次操作,推到「更长的前缀」(i, j)。leetcode+1

一旦把「dp 里的 i/j = 前缀长度」和「字符串下标 = i-1/j-1」区分开,就不会再被这些下标搞乱。


你可以直接把这篇博客贴到 CSDN,然后根据自己的理解再加上图(例如 DP 表格截图)或者手写示意图,会更容易被读者看懂。


参考链接

  • LeetCode 72. Edit Distance
  • LeetCode 72. Edit Distance - 提交记录
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/28 11:33:55

TextIn大模型加速器+火山引擎: 文档结构化数据处理工具扣子智能体工作流创建指南

TextIn大模型加速器火山引擎: 文档结构化数据处理工具扣子智能体工作流创建指南 背景 随着“数字员工”的全面上岗&#xff0c;合合信息与火山引擎联合推出的“大模型加速器”升级版TextIn xParse插件正式发布。这一工具为企业与开发者提供了强大的AI工程化能力&#xff0c;帮…

作者头像 李华
网站建设 2026/1/28 7:53:22

HeyGem系统提供[特殊字符]️删除按钮与[特殊字符]打包下载双功能设计贴心

HeyGem系统如何用“删除”与“打包下载”提升AI视频生产体验 在数字人技术逐渐走入日常内容生产的今天&#xff0c;越来越多的创作者、企业培训师和营销人员开始依赖AI生成口型同步视频。这类工具的核心能力——将一段音频驱动成人物自然说话的画面——早已不是秘密。真正拉开差…

作者头像 李华
网站建设 2026/1/29 23:12:43

HeyGem系统输出可用于HTML页面嵌入播放展示

HeyGem系统输出可用于HTML页面嵌入播放展示 在企业数字化转型加速的今天&#xff0c;官网、H5页面和内部管理系统对动态内容的需求日益增长。尤其是产品介绍、员工讲解、智能客服等场景中&#xff0c;传统真人拍摄视频不仅成本高、周期长&#xff0c;还难以实现批量个性化定制。…

作者头像 李华
网站建设 2026/1/31 6:30:38

一文说清Arduino蜂鸣器音乐代码工作原理

让蜂鸣器“唱歌”的秘密&#xff1a;深入剖析 Arduino 音乐实现原理你有没有试过用一块几块钱的 Arduino 和一个小小的蜂鸣器&#xff0c;让设备“唱”出《小星星》&#xff1f;听起来像魔法&#xff0c;但其实背后是一套清晰、可理解的技术逻辑。这不仅是个有趣的创客项目&…

作者头像 李华
网站建设 2026/2/1 4:38:07

HeyGem系统服务器IP替换localhost实现远程访问

HeyGem系统服务器IP替换localhost实现远程访问 在企业级AI应用部署中&#xff0c;一个看似简单的“从本地访问到远程可用”的转变&#xff0c;往往决定了整套系统的实用边界。比如HeyGem数字人视频生成系统——它基于音频驱动口型同步技术&#xff0c;能高效生成高质量的虚拟人…

作者头像 李华
网站建设 2026/1/29 23:08:13

HeyGem系统支持YOLOv5人脸识别预处理模块接入

HeyGem系统集成YOLOv5&#xff1a;打造高鲁棒性数字人视频预处理新范式 在虚拟主播、在线教育和智能客服快速普及的今天&#xff0c;用户对数字人“拟真度”的要求已从“能说话”迈向“像真人”。其中&#xff0c;口型与语音的精准同步&#xff08;Lip-sync&#xff09;成为衡量…

作者头像 李华