LeetCode 👉 HOT 100 👉 编辑距离 - 困难题

120 阅读4分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

题目

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

思路

最小编辑距离是 用作机器翻译和语音识别评价标准的基本算法。最直观的解法思路是,枚举所有可能的编辑方法,找出其中最小的那一个,但是这样的解法时间复杂度是指数级的,并不可取。

好在的是,动态规划却能很好的解决这个问题。再次回顾下动态规划的几个要素:

  • 确定dp元素的含义

  • 确定元素见的关系式

  • 确定初始值及边界条件

在确定这些要素之前,使用 A = horse, B = ros,举例,寻找一些规律

                        B        
            ---------------------               ---------------------           
            |   | ''| r | o | s |               |   | ''| r | o | s |
            ---------------------               ---------------------
            | ''|   |   |   |   |               | ''| 0 | 1 | 2 | 3 |
            ---------------------               ---------------------
            | h |   |   |   |   |    步骤一      | h |   |   |   |   |
            ---------------------  ---------->  ---------------------
        A   | o |   |   |   |   |               | o |   |   |   |   |
            ---------------------               ---------------------
            | r |   |   |   |   |               | r |   |   |   |   |
            ---------------------               ---------------------
            | s |   |   |   |   |               | s |   |   |   |   |
            ---------------------               ---------------------
            | e |   |   |   |   |               | e |   |   |   |   |
            ---------------------               ---------------------

上述步骤一,代表 A = '' 开始,依次转换为 '', 'r', 'ro', 'ros',分别需要 0, 1, 2, 3 次操作,因为很显然,每次都可以在之前的操作步骤之前,再进行一次 插入 操作即可

同理可以得到步骤二,也就是反过来,从 A = '', 'h', 'ho', 'hor', 'hors', 'horse',转换为 '',需要依次进行 0, 1, 2, 3, 4, 5 次删除操作即可。

                        B        
            ---------------------               ---------------------           
            |   | ''| r | o | s |               |   | ''| r | o | s |
            ---------------------               ---------------------
            | ''| 0 | 1 | 2 | 3 |               | ''| 0 | 1 | 2 | 3 |
            ---------------------               ---------------------
            | h | 1 |   |   |   |    步骤二      | h |   |   |   |   |
            ---------------------  ---------->  ---------------------
        A   | o | 2 |   |   |   |               | o |   |   |   |   |
            ---------------------               ---------------------
            | r | 3 |   |   |   |               | r |   |   |   |   |
            ---------------------               ---------------------
            | s | 4 |   |   |   |               | s |   |   |   |   |
            ---------------------               ---------------------
            | e | 5 |   |   |   |               | e |   |   |   |   |
            ---------------------               ---------------------

如果用一个 dp 数组来表示的话,上图已经求得了 dp[i][0], dp[0][j],其中 i < len(B), j < len(A),那么关键的问题来了,如何求得 dp[i][j]

举个例子:dp[1][1] 代表的就是 从 A = 'h' 变成 B = 'r',需要的最小编辑距离,在这时,A -> B 会有三种方式

  • 1、如果知道 A = '' 变成 B = 'r'(假设 X 步),那么 从 A = 'h'B = 'r',就只需要 X + 1步(对 A 进行一次删除操作);

  • 2、如果知道 A = 'h' 变成 B = ''(假设 Y 步),那么 从 A = 'h'B = 'r',就只需要 Y + 1步(对 B 进行一次添加操作);

  • 3、如果知道 A = '' 变成 B = ''(假设 Z 步),那么 从 A = 'h'B = 'r',就只需要 Z + 1步(对 A | B 进行一次替换操作);

    • 3.1 这里再思考,如果是 A = 'hor', B = 'r',此时也已知 A = 'ho' 变成 B = ''(假设为 Z 步),由于最后需要转换的字符相同,那么这个时候,不需要另外的操作。从A = 'hor' B = 'r' 依然是 Z

上面的规律总结一下,可以得到这样的元素间关系式:

dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1;   -------> word1[i] != word2[j];

dp[i][j] = dp[i - 1][j - 1];    -------> word1[i] == word2[j];

那么对于 dp 元素 dp[i][j]的含义,可以定义为 word1i 个字符,转换成 word2j 个字符,所需要的最短编辑距离

对于 初始条件,在上面的步骤一和步骤二也已经确定了,dp[i][0], dp[0][j]

边界条件就是 i < len(B), j < len(A)

根据这些条件,就可以完成算法了

完整代码如下

    /**
     * @param {string} word1
     * @param {string} word2
     * @return {number}
     */
    var minDistance = function (word1, word2) {
        // 定义边界条件,这里为了考虑空字符的问题,加整个长度加1
        const n = word1.length + 1, m = word2.length + 1;

        // 定义dp数组
        const dp = new Array(n).fill(0).map(item => new Array(m).fill(0));

        // 初始化边界条件
        for (let i = 0; i < n; i++) {
            dp[i][0] = i;
        }
        for (let j = 0; j < m; j++) {
            dp[0][j] = j;
        }

        // 填满 dp
        for (let i = 1; i < n; i++) {
            for (let j = 1; j < m; 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], dp[i][j - 1], dp[i - 1][j - 1]) + 1;
                }
            }
        }
        // dp最后一个元素即为所求
        return dp[n - 1][m - 1];
    };

小结

最短编辑距离,是一个非常基本的基本的算法;除了上面提到的被数据科学家广泛应用,其实很多实际的库,像 Vue 在比较新旧 Dom 的时候,也借鉴了最短编辑距离算法的思想。所以前端想要进阶,算法可能是一道坎,这道坎也很难,但是多了解一点,慢慢来吧~

# LeetCode 👉 HOT 100 👉 编辑距离 - 困难题

合集:LeetCode 👉 HOT 100,有空就会更新,大家多多支持,点个赞👍

如果大家有好的解法,或者发现本文理解不对的地方,欢迎留言评论 😄