题目描述
https://leetcode-cn.com/problems/delete-operation-for-two-strings/
解法
递归(自顶向下动态规划)
class Solution {
// 备忘录,消除重叠子问题
int[][] memo;
public int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
// 备忘录值为 -1 代表未曾计算
memo = new int[m][n];
for (int[] row : memo)
Arrays.fill(row, -1);
// 计算 s1[0..] 和 s2[0..] 的 lcs 长度
return dp(word1, 0, word2, 0);
}
// 递归函数,自顶向下的递推,其问题的定义:计算 s1[i..] 和 s2[j..] 的最少步数
int dp(String s1, int i, String s2, int j) {
if (i == s1.length() ){
return s2.length()-j;
}
if(j == s2.length()) {
return s1.length()-i;//全部删除,此时s2为“”,则需要把s1全删除
}
// 如果之前计算过,则直接返回备忘录中的答案
if (memo[i][j] != -1) {
return memo[i][j];
}
// 根据 s1[i] 和 s2[j] 的情况做选择
if (s1.charAt(i) == s2.charAt(j)) {
memo[i][j] = dp(s1, i + 1, s2, j + 1);
} else {
memo[i][j] = Math.min(
dp(s1, i + 1, s2, j)+1,//只需删除1个
dp(s1, i, s2, j + 1)+1//只需删除1个
//两个都删除的情况被包含在上面
);
}
return memo[i][j];
}
}
动态规划(自底向上)
class Solution {
public int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
//使用自顶向下和自底向上的dp数组定义方向相反
int[][]dp = new int[m+1][n+1];//表示使得s[0,m-1]和s[0,n-1]相同的最少步数
//边界条件:dp[0][n+1]和dp[m+1][0]表示有一个字符串是空串,则直接将另外一个全删除
for(int i=0;i<=m;i++){
dp[i][0] = i;
}
for(int i=0;i<=n;i++){
dp[0][i] = i;
}
//递推:
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,
dp[i][j-1]+1
);
}
}
return dp[m][n];
}
}
上面的动态规划里面,递推只与相邻的几个变量有关,我们可使用状态压缩来优化空间复杂度。
换个角度上思考:
题目让我们计算将两个字符串变得相同的最少删除次数,最后这两个字符串会被删成什么样子?删除得到的结果不就是它俩的最长公共子序列嘛!
那么,要计算删除的次数,就可以通过最长公共子序列的长度推导出来:
int minDistance(String s1, String s2) {
int m = s1.length(), n = s2.length();
// 复用前文计算 最长公共子序长度的函数
int lcs = longestCommonSubsequence(s1, s2);
return m - lcs + n - lcs;
}