0072.编辑距离

难度:🟡 中等

标签字符串动态规划

链接72. 编辑距离

题目描述

给你两个单词 word1word2, 请返回将 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])所需的最少操作数。

  • 状态转移 的逻辑如下:

    1. 如果 word1[i-1]word2[j-1] 相等,说明最后一个字符无需操作,问题退化为计算 word1i-1 个字符与 word2j-1 个字符的编辑距离。即 dp[i][j] = dp[i-1][j-1]

    2. 如果 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

关键步骤

  1. 初始化:创建一个 (m+1) x (n+1)dp 数组,其中 mn 分别是 word1word2 的长度。

  2. 设置边界条件

    • dp[i][0] = i:将长度为 iword1 转换为长度为 0 的 word2,需要 i 次删除操作。

    • dp[0][j] = j:将长度为 0 的 word1 转换为长度为 jword2,需要 j 次插入操作。

  3. 双层循环:遍历 dp 表,从 dp[1][1] 开始,根据上述状态转移方程填充每个 dp[i][j]

  4. 返回结果:遍历结束后,dp[m][n] 的值就是 word1word2 之间的最小编辑距离。

代码实现

/**
 * @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 表中相邻三个格子的状态,是掌握此算法的关键。

Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""