「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。
题目
给你两个单词 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')
思路
最小编辑距离是 用作机器翻译和语音识别评价标准的基本算法
。最直观的解法思路是,枚举所有可能的编辑方法,找出其中最小的那一个,但是这样的解法时间复杂度是指数级的,并不可取。
好在的是,动态规划却能很好的解决这个问题。再次回顾下动态规划的几个要素:
-
确定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
步
- 3.1 这里再思考,如果是
上面的规律总结一下,可以得到这样的元素间关系式:
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]的含义,可以定义为 word1
前 i
个字符,转换成 word2
前 j
个字符,所需要的最短编辑距离
对于 初始条件,在上面的步骤一和步骤二也已经确定了,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,有空就会更新,大家多多支持,点个赞👍
如果大家有好的解法,或者发现本文理解不对的地方,欢迎留言评论 😄