古法编程: 力扣72 编辑距离

0 阅读4分钟

力扣72:编辑距离:给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数。

其实也可以换一种说法叫评估两个单词之间相似的指标,可以使用莱文斯坦距离(Levenshtein distance)来进行计算是一种很经典的dp动态规划算法

思路分析

假设我们要计算字符串 a = "horse" 和 b = "ros" 的 Levenshtein 距离。

我们创建一个矩阵 dp,大小为 (len(a)+1) x (len(b)+1)。多出的一行一列表示空字符串。

dp[i][j] 定义:将字符串 a 的前 i 个字符,转换成字符串 b 的前 j 个字符,所需的最少编辑次数。

初始化其基本值,添加空串

  • 将 a 的前 i 个字符变成空串,需要 i 次删除。即 dp[i][0] = i
  • 空串变成 b 的前 j 个字符,需要 j 次插入。即 dp[0][j] = j

image.png

逐格填充矩阵

从 i=1, j=1 开始,按照从左到右、从上到下的顺序,逐一计算 dp[i][j] 的值。对于每个格子,我们进行如下判断:

  • 如果 a[i-1] 等于 b[j-1]
    当前字符相同,不需要任何编辑操作,直接继承左上角的值
    dp[i][j] = dp[i-1][j-1]

  • 如果 a[i-1] 不等于 b[j-1]
    我们需要执行一次编辑。取以下三种操作中的最小值,然后加1

    1. 删除:先删除 a[i-1],然后用 a 的前 i-1 个字符去匹配 b 的前 j 个字符。成本 = dp[i-1][j] + 1
    2. 插入:先用 a 的前 i 个字符匹配 b 的前 j-1 个字符,然后在 a 的末尾插入 b[j-1]。成本 = dp[i][j-1] + 1
    3. 替换:先不管最后一个字符,用 a 的前 i-1 个字符匹配 b 的前 j-1 个字符,然后把 a[i-1] 替换成 b[j-1]。成本 = dp[i-1][j-1] + 1

具体解释

image.png

1. 删除操作

思路:先把碍事的 'h' 删掉,问题就变成了一个我们已经解决过的更简单的问题。

  • 第一步:删除 a 的最后一个字符 a[0]='h'。(花费 1 次操作)
  • 现在a 变成了空字符串 ""
  • 新问题:把 "" 变成 "r",这个问题的答案我们已经算好,存放在 dp[0][1] 里,值是 1
  • 总成本dp[0][1] + 1 = 1 + 1 = 2

一句话理解:既然 'h' 不是 'r',那干脆把 'h' 扔了,看剩下的部分怎么变。

2. 插入操作

思路:先假装没有 'h' 这个麻烦,把前面的部分变好,最后再插入 'r'

  • 第一步:先用 a 的前1个字符 "h" 去匹配 b 的前0个字符 ""。也就是把 "h" 变成空字符串 "",这个问题的答案存放在 dp[1][0] 里,值是 1
  • 现在:我们成功地把 "h" 变成了 ""
  • 第二步:在末尾插入我们需要的 b[0]='r'。(花费 1 次操作)
  • 总成本dp[1][0] + 1 = 1 + 1 = 2

一句话理解:先别管目标字符 'r',把前面的活儿干完,最后把 'r' 硬加上去。

3. 替换操作

思路:同时忽略 a 和 b 的尾巴,把前面的问题解决好,然后直接把尾巴“变”过去。

  • 第一步:先解决一个更简单的问题:把 a 的前0个字符 "" 变成 b 的前0个字符 ""。这个问题的答案存放在 dp[0][0] 里,值是 0
  • 现在:我们处理好了一无所有的“前半部分”。
  • 第二步:处理尾巴。我们手里有一个 'h',目标是一个 'r'。我们不删也不加,直接把 'h' 替换成 'r'。(花费 1 次操作)
  • 总成本dp[0][0] + 1 = 0 + 1 = 1

一句话理解:不管你们俩是啥,强行把前面的搞定,然后直接“指鹿为马”,把尾巴 'h' 强行改成 'r'

代码实现

public class LevenshteinDistance {
    public int minDistance(String word1, String word2) {
        final int rows = word1.length() + 1, cols = word2.length() + 1;
        int[][] dp = initDp(rows, cols);

        // 从左到右,从上到下
        for (int i = 1; i < rows; i++) {
            for (int j = 1; j < cols; j++) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    // 删除、插入、替换
                    dp[i][j] = Math.min(dp[i - 1][j], Math.min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
                }
            }
        }
        return dp[rows - 1][cols - 1];
    }

    private int[][] initDp(int rows, int cols) {
        int[][] dp = new int[rows][cols];
        for (int i = 0; i < rows; i++) {
            dp[i][0] = i;
        }
        for (int j = 0; j < cols; j++) {
            dp[0][j] = j;
        }
        return dp;
    }

    public static void main(String[] args) {
        LevenshteinDistance solution = new LevenshteinDistance();
        System.out.println(solution.minDistance("horse", "ros")); // 3
        System.out.println(solution.minDistance("intention", "execution")); // 5
    }
}