https://oj.leetcode.com/problems/edit-distance/
Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.)
You have the following 3 operations permitted on a word:
a) Insert a character
b) Delete a character
c) Replace a character
public int minDistance(String word1, String word2)
这种dp题目之前已经介绍过类似的一些了。同样,可以分为暴力解和dp解。
下面先给出暴力解的做法,首先要声明的是暴力解通常在leetcode里面都是走不通的,哪怕算法对了基本也就是一个TLE的命。最低限度是要做到截枝的。而截肢其实等于DP的开始,memorization。
首先说一下这题,这样的题目肯定是两个单词从头开始往后匹配着走的。那么如果说i是word1的当前长度,j是word2的当前长度。a表示的其实就是j不变的情况下,i + 1走入下一层递归。b条件表示的就是i不变的情况下j + 1走入下一层,c条件表示的就是i + 1, j + 1同时走入下一层。当然以上三种情况都只会在word1[i] != word2[j]的情况下才去考虑的,操作数+1。 word1[i] == word2[j]的时候也是i + 1, j + 1走入下一层,但是操作数不变。暴力递归的方式实际上就是在遇到word1[i] != word2[j]的时候产生三个递归分支,取其中返回的最小值,然后加1。而在word1[i] == word2[j]的时候直接返回下一层的计算结果。截枝所要做的其实就是防止重复递归,也就是譬如当我第一次走到word1[i], word2[j]的时候,我就保留我得到的结果,下一层再进入同样条件的时候,我可以直接返回我储存好的结果。所以为了截枝,我实际上需要一个二维整型数组cached[word1.length()][word2.length]保留我的状态。给出代码如下:
public int minDistance(String word1, String word2) {
if(word1.isEmpty() || word2.isEmpty())
return word1.isEmpty() ? word2.length() : word1.length();
int[][] cache = new int[word1.length()][word2.length()];
for(int i = 0; i < cache.length; i++)
for(int j = 0; j < cache[0].length; j++)
cache[i][j] = -1;
return helper(word1, word2, 0, 0, cache);
}
public int helper(String p, String s, int p_pos, int s_pos, int[][] cached){
if(p.length() == p_pos || s.length() == s_pos)
return p_pos == p.length() ? s.length() - s_pos : p.length() - p_pos;;
int res = 0;
if(cached[p_pos][s_pos] != -1)
return cached[p_pos][s_pos];
else if(p.charAt(p_pos) == s.charAt(s_pos)){
res = helper(p, s, p_pos + 1, s_pos + 1, cached);
}else{
res = Math.min(helper(p, s, p_pos + 1, s_pos, cached), Math.min(helper(p, s, p_pos, s_pos + 1, cached), helper(p, s, p_pos + 1, s_pos + 1, cached))) + 1;
}
cached[p_pos][s_pos] = res;
return res;
}
绝大部分求数量不求过程的暴力递归是可以根据其递归的条件直接得到我们的DP的条件的。
首先给出递归式
f(0, i) = i, 这表示word1取空子集的时候,word2取i长度的子串的时候。word1需要insert i次。
f(i, 0) = i, 这表示word1取i长度的子串的时候,word2取空集的时候,word1需要delete i次。
f(i, j) = f(i - 1, j - 1) if(word1[i] == word2[j]),
f(i, j) = min(f(i, j - 1), f(i - 1, j), f(i - 1, j - 1)) + 1。 以上这两个推导式实际上就是暴力递归的条件的翻版。
于是乎,我来给代码了:
public int minDistance(String word1, String word2) {
if(word1.isEmpty() || word2.isEmpty())
return word1.isEmpty() ? word2.length() : word1.length();
int[][] dp = new int[word1.length() + 1][word2.length() + 1];
for(int i = 0; i < dp.length; i++){
dp[i][0] = i;
}
for(int i = 0; i < dp[0].length; i++)
dp[0][i] = i;
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], dp[i - 1][j])) + 1;
}
}
return dp[word1.length()][word2.length()];
}另外,所有二维DP在单向推导的情况下都可以化为一维。于是乎,流氓来了:
public int minDistance(String word1, String word2) {
if(word1.isEmpty() || word2.isEmpty())
return word1.isEmpty() ? word2.length() : word1.length();
String longer = word1.length() > word2.length() ? word1 : word2;
String shorter = longer == word1 ? word2 : word1;
int[] dp = new int[shorter.length() + 1];
int[] backup_dp = new int[shorter.length() + 1];
for(int i = 0; i < dp.length; i++)
dp[i] = i;
for(int i = 1; i <= longer.length(); i++){
// int[] backup_dp = new int[shorter.length() + 1];
backup_dp[0] = i;
for(int j = 1; j <= shorter.length(); j++){
if(longer.charAt(i - 1) == shorter.charAt(j - 1)){
backup_dp[j] = dp[j - 1];
}else{
backup_dp[j] = Math.min(dp[j - 1], Math.min(backup_dp[j - 1], dp[j])) + 1;
}
}
swapper(dp, backup_dp);
// dp = backup_dp;
}
return dp[shorter.length()];
}
public void swapper(int[] dp, int[] dp2){
for(int i = 0; i < dp.length; i++){
dp[i] ^= dp2[i];
dp2[i] ^= dp[i];
dp[i] ^= dp2[i];
}
}这里唯一需要注意的就是我们需要两个一维数组。这个本质上的原因是因为f(i, j - 1),f(i - 1, j - 1),f(i - 1,j) 这三个状态是不可能同时用一个一维数组表示的,所以我们需要另一个数组表示上一层状态。但这也是O(N)的空间。
本文详细阐述了使用动态规划解决编辑距离问题的方法,并对比了暴力递归与DP解法,提供了从递归到DP的转换过程及优化技巧。
137

被折叠的 条评论
为什么被折叠?



