95.编辑距离

5 阅读2分钟

题目链接

给你两个单词 word1word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

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

解法 动态规划+滚动数组

思路

dp数组的定义为 dp[i][j] 表示了 word1[0..i-1] 转换到 word2[0..j-1] 的最小操作数。 为什么是 i-1j-1呢?因为要将第一列和第一行表示为空字符串。

所以dp数组在初始化的时候要初始化第一行和第一列。

然后再思考状态转移方程:如果当前字符相等,那么不需要增加操作数。反之,则要更新 dp[i][j]

  • 删除 word1[i - 1],即 dp[i - 1][j] + 1
  • 插入 word2[j - 1],即 dp[i][j - 1] + 1
  • 替换 word1[i - 1] -> word2[j - 1],即 dp[i - 1][j - 1] + 1

这三种操作的最小操作步数。

当然这是经典动态规划的方法,还有利用滚动数组来优化空间。仔细观察会发现 dp[i][j] 只与上一行相关,所以可以将二维数组优化为两个一维数组。

代码

经典动态规划

function minDistance(word1: string, word2: string): number {
    const m = word1.length;
    const n = word2.length;
    const dp = Array.from({length: m + 1}, () => Array(n + 1).fill(-1));

    for (let i = 0; i <= n; i++) {
        dp[0][i] = i;
    }

    for (let i = 0; i <= m; i++) {
        dp[i][0] = i;
    }

    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 {
                dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1);
            }
        }
    }

    return dp[m][n];
};

滚动数组优化空间

function minDistance(word1: string, word2: string): number {
    const m = word1.length;
    const n = word2.length;
    let prev = new Array(n + 1).fill(0);  // 上一行
    let curr = new Array(n + 1).fill(0);  // 当前行


    for (let i = 0; i <= n; i++) {
        prev[i] = i;
    }

    for (let i = 1; i <= m; i++) {
        curr[0] = i; // 空串转换成 word1 的前 i 个字符,需要 i 次删除
        for (let j = 1; j <= n; j++) {
            if (word1[i - 1] === word2[j - 1]) {
                curr[j] = prev[j - 1];
            } else {
                curr[j] = 1 + Math.min(prev[j], prev[j - 1], curr[j - 1]);
            }
        }
        [prev, curr] = [curr, prev];
    }

    return prev[n];
};

时空复杂度

时间复杂度:O(m * n)

空间复杂度:经典动态规划 O(m * n),滚动数组 O(n)