让我们先用 分治(divide and conquer) 的思想来分解一下子问题。然后用搜索解决这些子问题。
设有两个字符串 A,B,长度分别为 La,Lb。我们的每次操作都让 A,B 的相同后缀尽可能的长。
举几个例子理解下相同后缀:
"abc" 和 "uio" 的相同后缀为空串 ""。
"hjk" 和 "hjk" 的相同后缀有 "hjk", "jk", "k", ""。
因为在A中增/删字符和在B中删/增 是等价的,同样,在A,B中替换字符也是等价的。所以我们只操作 A,这可以使问题变得容易思考。
数据预定义
设 suffix 为 A,B的相同后缀,对于任意的字符串,必有相同后缀 suffix = ""。
当 suffix == B 时,且 La == Lb 时, 必有 A == B。
所以我们的目标是,在经过若干次操作后,让 suffix == B,且 A 的长度变为 Lb。
初始时,设 suffix = "", ls = suffix.size(),pa = La - ls, pb = Lb -ls;
pa 和 pb 代表字符串 A,B 拿掉 suffix 后的剩余长度,即将要被处理的前缀部分。
分解子问题
尝试将 B[pb] (假设下标从 1 开始) 归入到 suffix 中,那么面临两种情况:
B[pb] 和 A[pa] 相等,此时无需操作,有 suffix = B[pb] + suffix。pa = pa-1, pb = pb-1。
B[pb] 和 A[pa] 不相等,根据题意我们有三种操作方法:
替换:A[pa] = B[pb],此时显然 A[pa] == B[pb]。
所以有 suffix = B[pb] + suffix。pa = pa - 1, pb = pb - 1。
插入:在A[pa] 后插入字符 B[pb],此时显然有 A[pa+1] == B[pb]。
所以有 suffix = B[pb] + suffix。pa = pa, pb = pb - 1。
删除:删除A[pa]。此时无法保证 A [pa-1] == B[pb]。
所以有 suffix = suffix,pa = pa-1, pb = pb。
也就是说我们可以将问题分解为四个子问题。
我们用 match(A, B, pa, pb) 代表字符串 A,B 的剩余前缀长度为 pb, pb 时,达到目标的最小操作次数。
那么 match(A,B,pa,pb) 的解根据上述讨论的两种情况,可以分解为:
B[pb] == A[pa],对应的子问题为 match(A,B,pa-1,pb-1);
B[pb] != A[pa],
替换:对应子问题 match(A,B,pa-1,pb-1)
插入:对应子问题 match(A,B,pa,pb-1)
删除:对应子问题 match(A,B,pa-1,pb)
那么可以得到一个递归式子。为描述简单用 f(pa,pb) 代表 match(A,B,pa,pb):
特别的,当 pa == 0 或 pb == 0 时,最优解显而易见为 f(pa,pb) = max(pa,pb)。
所以完整的递推式子为:
解决重复子问题的计算
当一个子问题可以由多个较大问题分解而来时,面临着重复计算的问题,此时的计算总次数可能会随着分解深度增加而指数级增长~
记忆化搜索是解决这个问题的一种方法,即对于每个已经求解的子问题,把答案保存下来!这里是不是就有点动态规划中dp数组的味道了?(:有内味了吗?
图示
作者:Time-Limit
链接:https://leetcode-cn.com/problems/edit-distance/solution/dong-tai-gui-hua-de-ben-zhi-shi-di-gui-ha-xi-_-by-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
package leetCodeTest;
public class 编辑距离 {
public static void main(String[] args) {
String word1 = "horse";//intention
String word2 = "ros";//execution
int i = minDistance(word1, word2);
System.out.println("i = " + i);
}
public static int minDistance(String word1, String word2) {
int len1 = word1.length(), len2 = word2.length();
int[][] dp = new int[len1 + 1][len2 + 1];
for (int i = 0; i <= len1; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= len2; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]);
}
}
}
return dp[len1][len2];
}
}
其余详细解释参考:https://leetcode-cn.com/problems/edit-distance/solution/bian-ji-ju-chi-by-leetcode-solution/