题目
给你两个单词 word1
和 word2
, 请返回将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
思路分析
编辑距离问题就是给定两个字符串 s1 和 s2,只能用三种操作把 s1 变成 s2,求最少的操作数。需要明确的是,不管是把 s1 变成 s2 还是反过来,结果都是一样的,所以下面就以 s1 变成 s2 举例。
解决两个字符串的动态规划问题,一般都是用两个指针 i, j 分别指向两个字符串的最后,然后一步步往前走,缩小问题的规模。
设两个字符串分别为 "rad" 和 "apple",为了把 s1 变成 s2,算法会这样进行:
当 s1[i] == s[j] 时,什么都不做(skip);
j 走完 s2,但 i 还没走完 s1,那么就用删除操作把 s1 缩短为 s2。
i 走完 s1,但 j 还没走完 s2,那么就用插入操作把 s2 剩下的字符全部插入 s1。
代码详解
base case 是 i 走完 s1 或 j 走完 s2,可以直接返回另一个字符串剩下的长度。
对于每对字符 s1[i] 和 s2[j],可以有4种操作:
if s1[i] == s2[j]:
什么都不做(skip)
i, j 同时向前移动
else:
三选一:
插入(insert)
删除(delete)
替换(replace)
”状态“就是算法在推进过程中会变化的变量,显然这里就是指针 i 和 j 的位置。
”选择“就是对于每一个状态,也就是 skip,insert,delete,replace 这4种操作做出选择。
递归代码
package DynamicProgramming;
// leetcode 72 编辑距离
// 暴力解法,递归(超出时间限制)
public class EditDistance {
private String s1;
private String s2;
public int minDistance(String s1, String s2) {
this.s1 = s1;
this.s2 = s2;
// i, j 初始化指向最后一个索引
return dp(s1.length() - 1, s2.length() - 1);
}
public int dp(int i, int j) {
// base case,递归终止条件
if (i == -1) {
return j + 1;
}
if (j == -1) {
return i + 1;
}
// 做选择,递归操作
if (s1.charAt(i) == s2.charAt(j)) {
return dp(i - 1, j - 1); // 什么都不做,i 和 j 向前移动一位
}
return Math.min(dp(i - 1, j - 1) + 1, // 替换
Math.min(dp(i, j - 1) + 1, // 插入
dp(i - 1, j) + 1) // 删除
);
}
public static void main(String[] args) {
EditDistance editDistance = new EditDistance();
int count = editDistance.minDistance("rad", "apple");
System.out.println(count);
}
}
引入 DP table
首先明确 dp 数组的含义,dp 数组是一个二维数组,长这样:
dp[...][0] 和 dp[0][...] 对应 base case,dp[i][j] 的含义和之前的 dp 函数类似。dp 函数的 base case 是 i, j 等于-1,而数组索引至少是0,所以 dp 数组会偏移一位。既然 dp 数组和递归 dp 函数含义一样,也就可以直接套用之前的思路写代码,唯一不同的是,DP table 是自底向上求解,递归解法是自顶向下求解:
package DynamicProgramming;
// 引入 dp table
public class EditDistance {
public int minDistance(String word1, String word2) {
int m = word1.length();
int n = word2.length();
int[][] dp = new int[m + 1][n + 1];
// base case
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 - 1] + 1,
Math.min(dp[i][j - 1] + 1,
dp[i - 1][j] + 1)
);
}
}
}
// 存储整个 s1 和 s2 的最小编辑距离
return dp[m][n];
}
public static void main(String[] args) {
EditDistance editDistance = new EditDistance();
int count = editDistance.minDistance("rad", "apple");
System.out.println(count);
}
}