题目
给你两个单词 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 casefor (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);}}