0072.编辑距离
难度:🟡 中等
标签:字符串
、动态规划
链接:72. 编辑距离
题目描述
给你两个单词 word1
和 word2
, 请返回将 word1
转换成 word2
所使用的最少操作数。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
解题思路
核心思想
本题是动态规划领域的里程碑式问题,旨在计算两个字符串之间的编辑距离 (Edit Distance)。核心思想是通过解决子问题——即计算两个字符串前缀之间的编辑距离——来逐步构建出整个问题的解。
思路选择
动态规划 (Dynamic Programming) 是解决此问题的标准最优思路。
我们可以定义一个二维
dp
表,其中dp[i][j]
表示将word1
的前i
个字符(word1[0...i-1]
)转换为word2
的前j
个字符(word2[0...j-1]
)所需的最少操作数。状态转移 的逻辑如下:
如果
word1[i-1]
与word2[j-1]
相等,说明最后一个字符无需操作,问题退化为计算word1
前i-1
个字符与word2
前j-1
个字符的编辑距离。即dp[i][j] = dp[i-1][j-1]
。如果
word1[i-1]
与word2[j-1]
不相等,我们需要在三种操作中选择成本最低的一种:替换:将
word1[i-1]
替换为word2[j-1]
。成本为dp[i-1][j-1] + 1
。删除:删除
word1
的第i
个字符。成本为dp[i-1][j] + 1
。插入:在
word1
的第i
个字符后插入一个字符。成本为dp[i][j-1] + 1
。 我们取这三者中的最小值,即dp[i][j] = Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
。
关键步骤
初始化:创建一个
(m+1) x (n+1)
的dp
数组,其中m
和n
分别是word1
和word2
的长度。设置边界条件:
dp[i][0] = i
:将长度为i
的word1
转换为长度为 0 的word2
,需要i
次删除操作。dp[0][j] = j
:将长度为 0 的word1
转换为长度为j
的word2
,需要j
次插入操作。
双层循环:遍历
dp
表,从dp[1][1]
开始,根据上述状态转移方程填充每个dp[i][j]
。返回结果:遍历结束后,
dp[m][n]
的值就是word1
和word2
之间的最小编辑距离。
代码实现
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
var minDistance = function (word1, word2) {
const m = word1.length;
const n = word2.length;
// dp[i][j] 表示 word1 的前 i 个字符到 word2 的前 j 个字符的编辑距离
const dp = new Array(m + 1).fill(null).map(() => new Array(n + 1).fill(0));
// 初始化边界条件
for (let i = 1; i <= m; i++) {
dp[i][0] = i;
}
for (let j = 1; j <= n; j++) {
dp[0][j] = j;
}
// 填充 dp 表
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (word1[i - 1] === word2[j - 1]) {
// 字符相同,无需操作
dp[i][j] = dp[i - 1][j - 1];
} else {
// 字符不同,取三种操作的最小值 + 1
dp[i][j] = Math.min(
dp[i - 1][j], // 删除
dp[i][j - 1], // 插入
dp[i - 1][j - 1] // 替换
) + 1;
}
}
}
return dp[m][n];
};
复杂度分析
时间复杂度:O(m × n) 其中 m 和 n 分别是两个字符串的长度。我们需要填充整个
dp
表。空间复杂度:O(m × n) 我们需要一个二维数组来存储
dp
表。可以通过状态压缩将空间复杂度优化到 O(min(m, n))。
相关题目
总结
本题是动态规划中关于字符串编辑问题的奠基之作,其状态转移方程深刻地体现了 DP “最优子结构”和“无后效性”的特点。理解插入、删除、替换三种操作如何对应到 dp
表中相邻三个格子的状态,是掌握此算法的关键。