一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情。
一.题目:
72. 编辑距离 给你两个单词
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 <= 500word1和word2由小写英文字母组成
二、思路分析:
首先这是一道难度为困难的题目,但是如果我们能够仔细审题的话,会发现它的做题思路是很简单的,即利用动态规划进行问题的求解。为什么这道题目需要用到动态规划呢?首先它是一道最值问题,设计最值问题的题目都可以试着利用动态规划进行求解,其次,对单词有可能进行三个操作,三个操作可能产生的效果不同会影响着下一步的结果,从子问题求解到大问题,就是动态规划的基本思路。
明确是用动态规划后,我们需要知道动态规划需要的四个要素:
- 确定dp数组的具体含义,我们设定的
dp数组为dp[i][j],dp[i][j]表示word1中0到i与word2中0到j的最少操作数 - 对dp数组进行必要的初始化,我们知道
word1或者word2只要其中一个是空字符的时候,另一个的操作数就是它的长度,所以我们利用这一点进行初始化 - 明确状态转移方程:如果
i,j前一位的字符相等时,我们就不需要进行处理直接赋值即可,因为我们不需要对其进行操作就能够符合要求,如果不是相等的话,我们明确对元素处理只有三个操作:插入、删除和替换,插入和删除实际的代码也是一样的,明确要么是word1中的单词字符需要插入或者删除或者word2中的单词字符需要插入或者删除,替换我们对任意一个单词的字符进行替换即可,然后比较得出最小值赋值给dp[i][j] - 明确遍历顺序,我们由下图得知
dp[i][j]由上方,左方和左上决定,所以我们的遍历顺序就是从左到右,从上到小的顺序。
三、代码:
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
var minDistance = function (word1, word2) {
let m = word1.length
let n = word2.length
//1.确定dp的大小及其含义:dp[i][j]表示word1中0到i与word2中0到j的最少操作数
let dp = Array.from(new Array(m+1), () => new Array(n+1).fill(0))
//2.明确初始化:我们知道word1或者word2其中一个是空字符的时候,另一个的操作数就是它的长度
for (let i = 0; i <= m; i++) {
dp[i][0] = i
}
for (let j = 0; j <= n; j++) {
dp[0][j] = j
}
//3.明确状态转移方程 插入、删除、替换
//4.有状态方程确定遍历顺序:从上到下,从左到右
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], dp[i][j - 1], dp[i - 1][j - 1]) + 1
}
}
}
return dp[m][n]
};
四、总结:
如果不熟悉动态规划的话,解决这类问题是很困难的,如果使用暴力算法那么必超时,所以学习动态规划,了解四要素还是很有必要的。