继续刷LeetCode 热题 HOT 100 的题目,并且在博客更新我的solutions。在csdn博客中我会尽量用文字解释清楚,相关Java代码大家可以前往我的个人博客jinhuaiyu.com中查看。
此题好难。
题目:编辑距离
给你两个单词 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 <= 500
word1 和 word2 由小写英文字母组成
solution:动态规划
单看这道题可能无从下手,肯定是不能暴力检查的。我们要提炼出两个规律:
1、所谓编辑距离,把word1转换成word2的步骤等于把word2转成word1的步骤,也等于word1和word2同时开始转换最后相通所用的最短步骤。这样一来,我们就可以将题目改成word1和word2同时可以修改,最后相同,也就是说,word1删除一个字符,等价于word2增加一个字符,反之亦然。
2、从哪个字符开始改会不会影响最终结果?答案是不会的。我们可以每一步都只动两个字符串的最后一个字符。因为增删改操作的顺序可以是任意的,如果确定某种转换方法是最短的(结合规律一,删word1和增word2这样的算同一种转换方法,本质上是一样的),那么我们完全可以从后往前依次执行对应操作,这样更容易将原问题转化为子问题。
有了这两个规律后,我们要提取出状态转移方程(转化为子问题),我们用 dp[i][j] 表示 word1 的前 i 个字母和 word2 的前 j 个字母之间的编辑距离。
情况1:如果我们知道dp[i-1][j]为a,那么dp[i][j]不会超过a+1。这种转换可以看作在word2的第j个字符后插入word1的第i个字符,然后剩下的a步就是dp[i-1][j]的那a步,这样一来,经过a+1步后,word1的前 i 个字母和word2前 j 个字母转化成一样的了。
情况2:显然a+1不一定是 word1 的前 i 个字母和 word2 的前 j 个字母之间最短的编辑距离。另外删word1的第i个字符和在word2第j个字符后加一个是同一种转换(规律一)。如果我们知道dp[i][j-1]为b,我们可以在word1的第i个字符后插入word2的第j个字符,剩下的b步就是dp[i][j-1]的那b步。这样一来,经过b+1步后也可以完成转换。
情况3:根据规律2,我们知道,我们可以通过调整转化步骤,从最后开始转化,这样并不会使最短编辑距离改变。因此,我们想讨论dp[i-1][j]能转化成的所有子问题,只需要关注word1和word2的第i和第j个字符的转化。前面已经讨论了两种,分别在其中一个后面插入(插入和删除是一回事),但还有最后一种,就是替换。如果我们知道dp[i-1][j-1]为c,那么dp[i][j]不可能超过c+1。因为多用的那一步可以看作是word1的第i个字符替换成word2的第j个字符。但如果这两个字符一样,这一步则不计,此时dp[i][j]=dp[i-1][j-1]。
综上,dp[i][j]可以转换成三种子情况中最小的那个,状态转移方程如下:
若 word1和 word2的最后一个字母相同:
dp[i][j]
=min(dp[i][j−1]+1,dp[i−1][j]+1,dp[i−1][j−1])
=1+min(dp[i][j−1],dp[i−1][j],dp[i−1][j−1]−1)
若 word1和 word2的最后一个字母不同:
dp[i][j]=1+min(dp[i][j−1],dp[i−1][j],dp[i−1][j−1])
对于边界情况,一个空串和一个非空串的编辑距离为 dp[i][0] = i 和 dp[0][j] = j,dp[i][0] 相当于对 word1 执行 i 次删除操作,dp[0][j] 相当于对 word1执行 j 次插入操作。
至此,我们就可以通过迭代,从i=0,j=0开始自底向上填充dp,最终得到dp[word1.length][word2.length]。
Finally,带有详细注释的代码放在我的个人博客http://jinhuaiyu.com/leetcode-edit-distance/