力扣解题思路:583. 两个字符串的删除操作

583. 两个字符串的删除操作


思路:删除两个字符串的字符使它们相等:

Input: "sea", "eat"
Output: 2
Explanation: You need one step to make "sea" to "ea" and another step to make "eat" to "ea".

———————————更新———————————
这一题,我又一次没有想到最长公共子序列的方法,采用了最土的递归法😭而且还超时!

int min = Integer.MAX_VALUE;
public int minDistance(String word1, String word2) {
    if(word1.length() == 0 && word2.length() == 0) return 0;
    if(word1.length() == 0) return word2.length();
    if(word2.length() == 0) return word1.length();
    //int[][] visited = new int[word1.length()][word2.length()];
    //for(int[] arr : visited) Arrays.fill(arr,-1);
    helper(word1,word2,0,0,0,visited);
    return min;
}
public void helper(String word1,String word2,int start1,int start2,int num){
    if(start1 == word1.length() && start2 == word2.length()){
        min = Math.min(min,num);
        return;
    }
    if(start1 == word1.length() || start2 == word2.length()){
        
        int len = start1 == word1.length()? word2.length() - start2 : word1.length() - start1;
        min = Math.min(min,len + num);
        return;
    }
    if(word1.charAt(start1) == word2.charAt(start2)){
        helper(word1,word2,start1+1,start2+1,num);
        helper(word1,word2,start1+1,start2,num+1);
        helper(word1,word2,start1,start2+1,num+1);
    }else{
        helper(word1,word2,start1+1,start2,num+1);
        helper(word1,word2,start1,start2+1,num+1);
    }
}

当我准备使用记忆化递归时发现我的递归函数并没有返回值。。。所以无法为visited数组赋值,因此我还是转战动态规划了。。。

public int minDistance(String word1, String word2) {
    if(word1.length() == 0 && word2.length() == 0) return 0;
    if(word1.length() == 0) return word2.length();
    if(word2.length() == 0) return word1.length();
    int[][] dp = new int[word1.length()+1][word2.length()+1];
    for(int i=1;i<=word1.length();i++) dp[i][0] = i;
    for(int i=1;i<=word2.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] = Math.min(dp[i-1][j-1],Math.min(dp[i-1][j]+1,dp[i][j-1]+1));
            }else{
                dp[i][j] = Math.min(dp[i-1][j]+1,dp[i][j-1]+1);
            }
        }
    }
    return dp[word1.length()][word2.length()];
}

然后就顺利一次通过,再次记录一下我的错误思路!

———————分割线(这里是年前写的)———————
这一题显然可以使用动态规划,这里提供两种动态规划的思路。我们用dp[i][j]表示的word1的前i个字符与word2的前j个字符达到相同所需的最小步数,更新规则如下:
(1)如果word1.charAt(i-1) == word2.charAt(j-1),那么不需要删除任何字符,dp[i][j]的值和dp[i - 1][j - 1]的值相同:dp[i][j] = dp[i - 1][j - 1];
(2)如果word1[i - 1] == word2[j - 1]不成立则需要考虑是删除word1.charAt(i-1)还是 word2.charAt(j-1),取其中最小的即可,但是无论word1.charAt(i-1) == word2.charAt(j-1)是否成立,都有:
dp[i][j] = Math.min(dp[i][j],Math.min(dp[i-1][j]+1,dp[i][j-1]+1));
然后按照更新规则更新即可:

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];
        }
        dp[i][j] = Math.min(dp[i][j],Math.min(dp[i-1][j]+1,dp[i][j-1]+1));
    }
}

但是这里需要注意的是,我们一定要对dp数组初始化,将最开始的所有值设置为最大值,如果不初始化则在遍历数组是min之后最小值永远是0,例如dp[i][0]和dp[0][j]也需要赋初始值(相当于一个空串和一个字符串比较,当然返回值就是那个字符串的长度了,也就是全部删去即可)

for(int i=0;i<=word1.length();i++) Arrays.fill(dp[i],Integer.MAX_VALUE);
for(int i=0;i<=word1.length();i++) dp[i][0] = i;
for(int j=0;j<=word2.length();j++) dp[0][j] = j;

完整代码如下:

public int minDistance(String word1, String word2) {
    int[][] dp = new int[word1.length()+1][word2.length()+1];
    for(int i=0;i<=word1.length();i++) Arrays.fill(dp[i],Integer.MAX_VALUE);
    for(int i=0;i<=word1.length();i++) dp[i][0] = i;
    for(int j=0;j<=word2.length();j++) dp[0][j] = j;
    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];
            }
            dp[i][j] = Math.min(dp[i][j],Math.min(dp[i-1][j]+1,dp[i][j-1]+1));
        }
    }
    return dp[word1.length()][word2.length()];
}

当然还有另一个比较巧妙地思路,将该题转换为求两个字符串的最长公共子序列问题,那最后结果就是删去除最长公共子序列的长度即可(之前写过最长公共子序列的题解,不懂的可以戳这里ο(=•ω<=)ρ⌒☆):

public int minDistance(String word1, String word2) {
    int m = word1.length(), n = word2.length();
    int[][] dp = new int[m + 1][n + 1];
    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] + 1;
            } else {
                dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
            }
        }
    }
    return m + n - 2 * dp[m][n];
}

另外,DFS也可以用来解这一题,也是先求最长公共子序列然后返回s1.length() + s2.length() - 2 * lcs(s1, s2, s1.length(), s2.length())即可:

public int minDistance(String s1, String s2) {
    return s1.length() + s2.length() - 2 * lcs(s1, s2, s1.length(), s2.length());//两个字符串的长度之和减去两倍公共部分即为需要移除的部分长度之和
}
public int lcs(String s1, String s2, int m, int n) {
    if (m == 0 || n == 0)
        return 0;
    if (s1.charAt(m - 1) == s2.charAt(n - 1))//两个字符串倒数第二个字符相同就意味着可以消除两个字符串最后的字母
        return 1 + lcs(s1, s2, m - 1, n - 1);
    else
        return Math.max(lcs(s1, s2, m, n - 1), lcs(s1, s2, m - 1, n));//若不相同则只消除某个字符串的最后一个字符,然后再作比较
}

当然也可以从字符串首部开始遍历:

public int minDistance(String s1, String s2) {
    return s1.length() + s2.length() - 2 * lcs(s1, s2, 0, 0);//两个字符串的长度之和减去两倍公共部分即为需要移除的部分长度之和
}
public int lcs(String s1, String s2, int m, int n) {
    if (m == s1.length() || n == s2.length())
        return 0;
    if (s1.charAt(m) == s2.charAt(n))//两个字符串倒数第二个字符相同就意味着可以消除两个字符串最后的字母
        return 1 + lcs(s1, s2, m + 1, n + 1);
    else
        return Math.max(lcs(s1, s2, m, n + 1), lcs(s1, s2, m + 1, n));//若不相同则只消除某个字符串的最后一个字符,然后再作比较
}

但是不幸的是,这两个DFS方法都超时了,接下来对DFS方法进行优化,我们采用记忆化回溯。我们知道在DFS过程中会不断的回溯,同的 i 和 j 值在不同的函数调用路径中会重复求解,尽管可能在前某次回溯中已经求解过该值了。只要相同的 i 和 j 对应的函数被调用一次,我们可以使用 memo 数组保存相应函数的返回值来避免后续访问的冗余。memo[i][j] 被用来保存函数调用 lcs(s1, s2, i, j)的返回值。实际上这就是变相的动态规划,而memo[i][j]相当于dp数组:

public int minDistance(String s1, String s2) {
    int[][] memo = new int[s1.length() + 1][s2.length() + 1];//记忆化
    return s1.length() + s2.length() - 2 * lcs(s1, s2, s1.length(), s2.length(), memo);
}
public int lcs(String s1, String s2, int m, int n, int[][] memo) {
    if (m == 0 || n == 0)
        return 0;
    if (memo[m][n] > 0)
        return memo[m][n];
    if (s1.charAt(m - 1) == s2.charAt(n - 1))
        memo[m][n] = 1 + lcs(s1, s2, m - 1, n - 1, memo);
    else
        memo[m][n] = Math.max(lcs(s1, s2, m, n - 1, memo), lcs(s1, s2, m - 1, n, memo));
    return memo[m][n];
}

这样就不会超出时间限制了,这证明通过返回已经在 memo 数组中保存的值,我们可以给搜索过程极大程度的剪枝。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值