动态规划-编辑距离
编辑距离也是动态规划的经典题目,通常需要计算从一个字符串通过一系列操作变成另一个字符串需要的最小步数。
或用来查看两个字符串的相似程度。
我们从一个简单的题目来看:
1.有两个单词 word1
和 word2
,每步可以删除任意一个字符串中的一个字符。请计算使得 word1
和 word2
相同所需的最小步数。
首先我们假设
word1=abcd
word2=pqrd
大家可以看到word1,word2两个字符串的最后一个字符都是d,也就是说如果我们想把word1变成word2,最后一个字符d是不用考虑的。
我们假设word1转换成word2的最小步数是dp[word1,word2]
那么dp[word1,word2]的最小步数就转换成了求word11(abc)和word21(pqr)的最小步数问题。
那么我们可以得到这种情况下的动态转移方程
dp[i,j]=dp[i-1,j-1] 此时word1[i]==word2[j]
我们再假设两个新的字符串
word1=abcd
word2=pqrs
显而易见,两个字符串的最后一个字符不相同,我们先对两个字符串都删除最后一个字符,得到
word11=abc
word21=pqr
那么dp[word1,word2]的最小步数就等于1+dp[word11->word2]和1+dp[word21,word1]两者之中的较小值
我们得到另一种情况的动态转移方程:
dp[i,j]=1+min{dp[i-1,j],dp[i,j-1]} 此时word1[i]!=word2[j]
好了,动态转移方程推导出来,代码基本上也就有了
class Solution { public int minDistance(String word1, String word2) { int m = word1.length(); int n = word2.length(); //定义并初始化dp数组 int [][] dp=new int[m+1][n+1]; dp[0][0]=0; //初始化某个字符串为空串的情况,两者的距离等于另一个字符的长度 for (int i = 1; i <=m; i++) { dp[i][0]=i; } for (int j = 1; j <= n; j++) { dp[0][j]=j; } for (int i = 1; i <=m; i++) { for (int j = 1; j <=n; 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])+1; } } } return dp[m][n]; } }
我们看一个进阶问题
2.如果有两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
可以对一个单词进行如下三种操作:插入一个字符,删除一个字符,替换一个字符
这个题目跟第一个基本相同,唯一的差别是我们可以做的操作多了两个,插入和替换。
还是同样的思路,如果word1,word2最后一个字符相同
那么dp[i,j]=dp[i-1,j-1]
如果word1,word2最后一个字符不同,那么
dp[word1->word2]可能有两种情况:
①可以由word1删除最后一个字符,变成word11,然后加上dp[word11->word2]的转换步数
②可以油word2删除最后一个字符,变成word21,然后加上dp[word21->word1]的转换步数
dp[i,j]=1+min{dp[i-1,j],dp[i,j-1]} 此时word1[i]!=word2[j]
慢!这个似乎少考虑了一种情况,因为我们现在多了一种替换操作。
③可以有word1替换最后一个字符为word2的最后一个字符,然后再加上dp[word11->word21]的转换步数
所有正确的动态转移方程是
dp[i,j]=1+min{dp[i-1,j],dp[i,j-1],dp[i-1,j-1]} 此时word1[i]!=word2[j]
class Solution { public int minDistance(String word1, String word2) { int m = word1.length(); int n = word2.length(); int[][] dp = new int[m+1][n+1]; dp[0][0] = 0; for (int j = 1; j <= n; j++) { dp[0][j] = j; } for (int i = 1; i <= m; i++) { dp[i][0] = i; } for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; 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 - 1] + 1, Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)); } } } return dp[m ][n ]; } }