代码随想录-动态规划-字符删除相关问题(Java)

判断子序列

链接: 392.判断子序列

思路

本题与最大子序和很像,只不过最大子序和是求公共的字符串,而本题其中一个字符串可能将另一个字符串包含在内。

但按最大子序和的思路也是可以的

递推公式不相等的时候,因为其中一个字符串比较短,一定被包含在内,所以我们不需要再从当前元素的上方元素和左方元素中取最大,而是最长的字符串它的下标左移一位。

遍历顺序:要根据递推公式来考虑遍历顺序

if(sChar[i - 1] == tChar[j - 1]){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else{
                    dp[i][j] = dp[i][j-1];
                }

元素都是由左上方 和 左方的元素推出来,所以从上至下,从左至右

具体s和t在内在外遍历时都可以

但是一定要注意统一

PS:还是按五部曲写一下子

不然自己写着写着晕了

代码

// s遍历在外,t遍历在内
class Solution {
    public boolean isSubsequence(String s, String t) {
        char[] sChar = s.toCharArray();
        char[] tChar = t.toCharArray();
        int[][] dp = new int[sChar.length + 1][tChar.length + 1];
        for(int i = 1; i <= sChar.length; i++){
            for(int j = 1; j <= tChar.length; j++){
                if(sChar[i - 1] == tChar[j - 1]){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else{
                    dp[i][j] = dp[i][j-1];
                }
            }
        }
        return dp[sChar.length][tChar.length] == sChar.length;
    }
}
// t遍历在外,s遍历在内
// 不止是遍历顺序,二维数组定义也要颠倒一下
// 还是按步骤来,不要想当然。起码在二维数组定义时要写清二维数组的含义。
class Solution {
    public boolean isSubsequence(String s, String t) {
        char[] sChar = s.toCharArray();
        char[] tChar = t.toCharArray();
        int[][] dp = new int[tChar.length + 1][sChar.length + 1];
        for(int i = 1; i <= tChar.length; i++){
            for(int j = 1; j <= sChar.length; j++){
                if(tChar[i - 1] == sChar[j - 1]){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else{
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[tChar.length][sChar.length] == sChar.length;
    }
}

不同的子序列

链接: 115.不同的子序列

思路

本题条件如果要求t子序列在s子序列是连续出现的,那么该问题就是KMP问题【最近复习到了KMP问题,但不太记得了,需要注意】

本题纯靠想的想不出来,需要在草稿纸上记录一下

按常规的动态规划五部曲

1.dp数组及下表的含义

二维数组:以i - 1结尾的s子序列中出现以j - 1为结尾的t的个数

2.确定递推公式

分为两种情况

s[i - 1]和t[j - 1]相等时

s[i - 1]和t[j - 1]不相等时

首先明确本题设置的二维数组求的是出现的个数,而不是出现序列的长度

当相等时

dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];

等号右侧前一部分很好理解,就是按照我们一般理解,当相等时,我们肯定看s字符和t字符的前一个字符的匹配情况

本题中因为s中可能出现重复的字母,所以我们需要左移s序列i - 2来与t中的j - 1进行比较;两者相加。

3.初始化

不要想当然,本题二维数组含义与判断子序列不同,因此初始化也不一样

t 去匹配 s

dp[i] [0] = 1; 一个空的字符串匹配有元素的字符串,结果为1

dp[0] [j] = 0;

dp[0] [0] = 1;

4.遍历顺序

由递推公式可以看出,二维数组都是由左上方和正上方推出来的

所以遍历的时候一定从上到下,从左到右。

5.举例推导:暂略

代码

class Solution {
    public int numDistinct(String s, String t) {
        char[] sChar = s.toCharArray();
        char[] tChar = t.toCharArray();
        int[][] dp = new int[sChar.length + 1][tChar.length + 1];
        for (int i = 0;i < sChar.length + 1;i++){
            dp[i][0] = 1;
        }
        for (int i = 1;i < sChar.length + 1;i++){
            for (int j = 1;j <tChar.length + 1;j++){
                if (sChar[i - 1] == tChar[j - 1]){
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                }else{
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[sChar.length][tChar.length];
    }
}

两个字符串的删除操作

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

思路

本题是求两个字符串相同所需的最小步数

有两种方法,一种是求最长公共子序列,然后用word1和word2长度相加-2倍的最长公共子序列长度。

第二种方法,就是动态规划的一般步骤

1.含义:下标为i - 1的word1和下标为j - 1的word2相同所需的最小步数

2.递推公式

如果相等,则等于该元素左上角的元素值

如果不等,分为三种情况

  • 下标i - 1的word1元素可能和j - 2的word2元素相等 + 1

  • i - 2 的元素可能和 j - 1的元素相等 + 1

  • i - 2 的元素可能和 j - 2 的元素(需要删除两个,+ 2

​ 此种情况可以倍前两种情况概括

代码

class Solution {
    public int minDistance(String word1, String word2) {
        // dp[i][j]含义:下标为i - 1的word1和下标为j - 1的word2相同所需的最小步数
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];
        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];
                }else{
                    // + 1逻辑上应该写在里面,之前漏掉后犯懒了
                    dp[i][j] = Math.min(dp[i][j - 1] ,dp[i - 1][j]) + 1;
                }
            }
        }
        return dp[word1.length()][word2.length()];
    }
}

编辑距离

链接: 72. 编辑距离

思路

本题思路比两个字符串的删除操作要复杂一点

同样可以对两个字符串进行操作,但是两个字符的删除操作只能进行删除操作,而编辑距离可以对字符串进行插入、删除、替换操作

递推公式有变化,其余和两个字符的删除操作类似。

在整个动规的过程中,最为关键就是正确理解dp[i][j]的定义!

if (word1[i - 1] != word2[j - 1])

  • 操作一:word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 再加上一个操作。

dp[i][j] = dp[i - 1][j] + 1;

  • 操作二:word2删除一个元素,那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 再加上一个操作。

dp[i][j] = dp[i][j - 1] + 1;

word2添加一个元素,相当于word1删除一个元素,例如 word1 = "ad" ,word2 = "a"word1删除元素'd'word2添加一个元素'd',变成word1="a", word2="ad", 最终的操作数是一样!

操作三:替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增删加元素。

可以回顾一下,if (word1[i - 1] == word2[j - 1])的时候我们的操作 是 dp[i][j] = dp[i - 1][j - 1] 对吧。

那么只需要一次替换的操作,就可以让 word1[i - 1] 和 word2[j - 1] 相同。【逆向思维】

所以 dp[i][j] = dp[i - 1][j - 1] + 1;

综上,当 if (word1[i - 1] != word2[j - 1]) 时取最小的,即:dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;

代码

class Solution {
    public int minDistance(String word1, String word2) {
        // dp[i][j]含义:下标为i - 1的word1和下标为j - 1的word2相同所需的最小步数
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];
        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];
                }else{
                    // + 1逻辑上应该写在里面,之前漏掉后犯懒了
                    dp[i][j] = Math.min(Math.min(dp[i][j - 1] ,dp[i - 1][j]),dp[i - 1][j - 1]) + 1;
                }
            }
        }
        return dp[word1.length()][word2.length()];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值