力扣72:编辑距离:给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数。
其实也可以换一种说法叫评估两个单词之间相似的指标,可以使用莱文斯坦距离(Levenshtein distance)来进行计算是一种很经典的dp动态规划算法
思路分析
假设我们要计算字符串 a = "horse" 和 b = "ros" 的 Levenshtein 距离。
我们创建一个矩阵 dp,大小为 (len(a)+1) x (len(b)+1)。多出的一行一列表示空字符串。
dp[i][j]定义:将字符串a的前i个字符,转换成字符串b的前j个字符,所需的最少编辑次数。
初始化其基本值,添加空串
- 将
a的前i个字符变成空串,需要i次删除。即dp[i][0] = i - 将空串变成
b的前j个字符,需要j次插入。即dp[0][j] = j
逐格填充矩阵
从 i=1, j=1 开始,按照从左到右、从上到下的顺序,逐一计算 dp[i][j] 的值。对于每个格子,我们进行如下判断:
-
如果
a[i-1]等于b[j-1]:
当前字符相同,不需要任何编辑操作,直接继承左上角的值。
dp[i][j] = dp[i-1][j-1] -
如果
a[i-1]不等于b[j-1]:
我们需要执行一次编辑。取以下三种操作中的最小值,然后加1:- 删除:先删除
a[i-1],然后用a的前i-1个字符去匹配b的前j个字符。成本 =dp[i-1][j] + 1 - 插入:先用
a的前i个字符匹配b的前j-1个字符,然后在a的末尾插入b[j-1]。成本 =dp[i][j-1] + 1 - 替换:先不管最后一个字符,用
a的前i-1个字符匹配b的前j-1个字符,然后把a[i-1]替换成b[j-1]。成本 =dp[i-1][j-1] + 1
- 删除:先删除
具体解释
1. 删除操作
思路:先把碍事的
'h'删掉,问题就变成了一个我们已经解决过的更简单的问题。
- 第一步:删除
a的最后一个字符a[0]='h'。(花费 1 次操作) - 现在:
a变成了空字符串""。 - 新问题:把
""变成"r",这个问题的答案我们已经算好,存放在dp[0][1]里,值是 1。 - 总成本:
dp[0][1] + 1 = 1 + 1 = 2
一句话理解:既然 'h' 不是 'r',那干脆把 'h' 扔了,看剩下的部分怎么变。
2. 插入操作
思路:先假装没有
'h'这个麻烦,把前面的部分变好,最后再插入'r'。
- 第一步:先用
a的前1个字符"h"去匹配b的前0个字符""。也就是把"h"变成空字符串"",这个问题的答案存放在dp[1][0]里,值是 1。 - 现在:我们成功地把
"h"变成了""。 - 第二步:在末尾插入我们需要的
b[0]='r'。(花费 1 次操作) - 总成本:
dp[1][0] + 1 = 1 + 1 = 2
一句话理解:先别管目标字符 'r',把前面的活儿干完,最后把 'r' 硬加上去。
3. 替换操作
思路:同时忽略
a和b的尾巴,把前面的问题解决好,然后直接把尾巴“变”过去。
- 第一步:先解决一个更简单的问题:把
a的前0个字符""变成b的前0个字符""。这个问题的答案存放在dp[0][0]里,值是 0。 - 现在:我们处理好了一无所有的“前半部分”。
- 第二步:处理尾巴。我们手里有一个
'h',目标是一个'r'。我们不删也不加,直接把'h'替换成'r'。(花费 1 次操作) - 总成本:
dp[0][0] + 1 = 0 + 1 = 1
一句话理解:不管你们俩是啥,强行把前面的搞定,然后直接“指鹿为马”,把尾巴 'h' 强行改成 'r'。
代码实现
public class LevenshteinDistance {
public int minDistance(String word1, String word2) {
final int rows = word1.length() + 1, cols = word2.length() + 1;
int[][] dp = initDp(rows, cols);
// 从左到右,从上到下
for (int i = 1; i < rows; i++) {
for (int j = 1; j < cols; 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], Math.min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
}
}
}
return dp[rows - 1][cols - 1];
}
private int[][] initDp(int rows, int cols) {
int[][] dp = new int[rows][cols];
for (int i = 0; i < rows; i++) {
dp[i][0] = i;
}
for (int j = 0; j < cols; j++) {
dp[0][j] = j;
}
return dp;
}
public static void main(String[] args) {
LevenshteinDistance solution = new LevenshteinDistance();
System.out.println(solution.minDistance("horse", "ros")); // 3
System.out.println(solution.minDistance("intention", "execution")); // 5
}
}