【简单】72. 编辑距离

0 阅读3分钟

给你两个单词 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')

提示:

  • 0 <= word1.length, word2.length <= 500
  • word1 和 word2 由小写英文字母组成

🏠 生活案例:乐高拼图改装

想象你手里有一排乐高积木组成的单词 word1(比如 "horse"),你的目标是把它改装成另一个单词 word2(比如 "ros")。

但是,改装是有代价的,每动一动手都要算作“1步”。你只能做三件事:

  1. 插入:往 word1 里塞一个新积木。
  2. 删除:从 word1 里扔掉一个积木。
  3. 替换:把 word1 里的一个积木换成另一种颜色/字母。

题目问的是:最少花多少步能改完?


💻 代码实现与生活化注释

这段代码的核心是建立一个“记账表格”(DP 数组),记录从 word1 的前 ii 个字符变到 word2 的前 jj 个字符最少需要几步。

JavaScript

/**
 * @param {string} word1
 * @param {string} word2
 * @return {number}
 */
var minDistance = function(word1, word2) {
    let m = word1.length;
    let n = word2.length;

    // 1. 建立一个二维“记账表格” dp[m+1][n+1]
    // dp[i][j] 表示:把 word1 的前 i 个字变成 word2 的前 j 个字要几步
    let dp = Array.from({length: m + 1}, () => Array(n + 1).fill(0));

    // 2. 初始化:如果其中一个单词是空的
    // word1 有 i 个字,变到空单词,只能全部“删除”,步数就是 i
    for (let i = 0; i <= m; i++) dp[i][0] = i;
    // 空单词变到 word2 有 j 个字,只能全部“插入”,步数就是 j
    for (let j = 0; j <= n; j++) dp[0][j] = j;

    // 3. 开始填表(动态规划的核心)
    for (let i = 1; i <= m; i++) {
        for (let j = 1; j <= n; j++) {
            // 如果当前的两个字母一模一样(比如都是 's')
            // 就像乐高颜色刚好对上了,不需要任何操作!
            // 步数直接继承“还没加这两个字母时”的步数
            if (word1[i - 1] === word2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                // 如果字母不一样,我们需要从三种方案里选个最省钱(步数最少)的:
                // dp[i-1][j-1] + 1 : 替换(把这个积木换掉)
                // dp[i][j-1] + 1   : 插入(往 word1 塞一个)
                // dp[i-1][j] + 1   : 删除(把 word1 现有的扔掉)
                dp[i][j] = Math.min(
                    dp[i - 1][j - 1], // 替换
                    dp[i][j - 1],     // 插入
                    dp[i - 1][j]      // 删除
                ) + 1; // 别忘了加上当前这一步的操作
            }
        }
    }

    // 4. 表格右下角的值就是我们要的最终答案
    return dp[m][n];
};

🧩 为什么这样有效?(状态转移图解)

动态规划的魅力在于**“利用已知的答案推算未知的答案”**。

当你计算 dp[i][j] 时,你其实是在看这三个格子:

  • 左边 dp[i][j-1] :代表“插入”操作。
  • 上边 dp[i-1][j] :代表“删除”操作。
  • 左上角 dp[i-1][j-1] :代表“替换”操作。

程序会自动对比这三条路,哪条路走的步数最少,就走哪条路。这就像导航软件会对比“高速、国道、小路”三条线,选一个最短时间到达终点一样。


复杂度分析

  • 时间复杂度O(m×n)O(m \times n)。你需要把这个表格里的每一个格子都填满。
  • 空间复杂度O(m×n)O(m \times n)。我们开辟了一个二维数组来存步数。

这个算法在拼写纠错、基因序列比对(看两个 DNA 序列有多像)等现实场景中都有大规模的应用!