给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符;
- 删除一个字符;
- 替换一个字符;
在此之前,我们要明确我们的实现思路:将 word2 固定不变,通过改变 word1 来使得 word1 变成 word2。
思路 (DP)
定义:dp[i][j] 为从 word1[0,…,i-1] 转换到 word2[0,…,j-1] 最小的编辑次数。i, j 的起始点从 1 开始。
有了如上的定义之后,我们需要考虑的有2个问题:第一个是 dp[i][j] 的边界问题;第二个问题是 dp[i][j] 的递归表达式。
首先是 dp[i][j] 的边界问题,即将一个非空字符串转换为一个空字符串,需要的最小编辑次数,套用我们之前的定义,就是将 word1[0,…,i-1] 转换为一个空字符串,对应 dp[i][j] 的值是多少,我们有:(作为初始化)
- dp[i][0] = i
- dp[0][j] = j
接下来我们对第二个问题进行分析:如何得到 dp[i][j] 的递归表达式?
我们如何求得 dp[i][j] 的值?我们利用的是动态规划的思想,即假设我们已经求得 dp[i][j] 之前的所有值(即已知 dp[0,…,i-1][0,…,j-1],并且 word1[0,…,i-2]==word2[0,…,j-2] ),那么我们就可以将问题拆解为一个个相同的小问题:考虑 word1[i-1] 与 word2[j-1]
如果 word1[i-1] == word2[j-1] ,那么相应的 dp[i][j] = dp[i-1][j-1]
如果 word1[i-1] != word2[j-1] ,那么问题变得有些复杂,需要一一分析:(我们需要考虑3种情况)
- 替换;需要对 word1[i-1] 进行替换,用 word2[j-1] 来替换 word1[i-1],那么 word1[0,…,i-1] == word2[0,…,j-1] ,此时: dp[i][j] = dp[i - 1][j - 1] + 1
- 删除;需要对 word1[i-1] 进行删除,使得 word1[0,…,i-2] == word2[0,…,j-1] ,此时: dp[i][j] = dp[i - 1][j] + 1 ;
- 插入;需要将 word2[j-1] 插入到 word1[i-1] 的后面,使得 word1[0,…,i-1] + word2[j-1] == word2[0,…,j-1] ,此时:dp[i][j] = dp[i][j - 1] + 1
最后我们将所有的递归公式写在一起:
- dp[i][0] = i , dp[0][j] = j
- 如果 word1[i-1] == word2[j-1] ,dp[i][j] = dp[i-1][j-1]
- 否则 dp[i][j] = min( dp[i][j] = dp[i - 1][j - 1] + 1, dp[i][j] = dp[i - 1][j] + 1, dp[i][j] = dp[i][j - 1] + 1 )
代码如下:
public class No72 {
public int minDistance(String word1, String word2) {
/**
* DP, 数组 dp[i][j] = x, 指的是 从 word1[0 ~ i-1] 转换到 word2[0 ~ j-1] 所需的最少操作次数(不包括 i, j)
*/
if (word2.length() == 0) { // 把 word1 中所有字母都删除
return word1.length();
}
if (word1.length() == 0) { // 在 word1 中添加 word2 中的每一个字母
return word2.length();
}
int[][] dp = new int[word1.length()+1][word2.length()+1];
// 初始化
for (int i = 0; i <= word1.length(); i++) {
dp[i][0] = i;
}
for (int i = 0; i <= word2.length(); i++) {
dp[0][i] = i;
}
//DP
for (int i = 1; i <= word1.length(); i++) {
for (int j = 1; j <= word2.length(); 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, Math.min(dp[i][j-1]+1, dp[i-1][j-1]+1));
}
}
}
return dp[word1.length()][word2.length()];
}
}