LeetCode算法题解(动态规划)|LeetCode392. 判断子序列、LeetCode115. 不同的子序列

一、LeetCode392. 判断子序列

题目链接:392. 判断子序列
题目描述:

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace""abcde"的一个子序列,而"aec"不是)。

示例 1:

输入:s = "abc", t = "ahbgdc"
输出:true

示例 2:

输入:s = "axc", t = "ahbgdc"
输出:false

提示:

  • 0 <= s.length <= 100
  • 0 <= t.length <= 10^4
  • 两个字符串都只由小写字符组成。
算法分析:
定义dp数组及下标含义:

dp[i][j]表示字符串s第i个下标之前的子串,与字符串t第j个下标之前的子串,相同的子序列长度。

递推公式:

如果s[i] == s[j],那么dp[i][j]可以由dp[i-1][j-1]+1推出来,

[0,i]区间的s子串与[0,j]区间的t子串相同的子序列长度等于[0,i-1]区间的s子串和[0,j-1]区间的t子串相同的子串序列长度加一;

如果s[i] != s[j],那么dp[i][j] 可以有两个方向推出来:

1、[0,i-1]区间的s子串与[0,j]区间的t子串的相同子序列的长度;

2、[0,i]区间的s子串与[0,j-1]区间的t子串的相同子序列的长度;

即dp[i][j]=max(dp[i-1][j],dp[i][j-1]);

初始化:

根据递推公式dp[i][j]=dp[i-1][j-1],dp[i][j]=max(dp[i-1][j],dp[i][j-1]),dp[i][j]可由它的左方、上方和左上放推出来,所以需要初始化dp数组的左边那一列和上边那一行。

        for(int i = 0; i < t.length(); i++){
            if(s.charAt(0) == t.charAt(i)){
                dp[0][i] = 1;
            }else if(i > 0){
                dp[0][i] = dp[0][i-1];
            }else{
                dp[0][i] = 0;
            }
        }
        for(int i = 0; i < s.length(); i++){
            if(s.charAt(i) == t.charAt(0)){
                dp[i][0] = 1;
            }else if(i > 0){
                dp[i][0] = dp[i-1][0];
            }else{
                dp[i][0] = 0;
            }
        }
遍历顺序:

字符串s和t的遍历顺序无所谓,不过都需从前往后遍历。

打印dp数组验证。(省略)

代码如下:

class Solution {
    public boolean isSubsequence(String s, String t) {
        if(s.length() == 0) return true;
        if(t.length() == 0) return false;
        int[][] dp = new int[s.length()][t.length()];
        for(int i = 0; i < t.length(); i++){
            if(s.charAt(0) == t.charAt(i)){
                dp[0][i] = 1;
            }else if(i > 0){
                dp[0][i] = dp[0][i-1];
            }else{
                dp[0][i] = 0;
            }
        }
        for(int i = 0; i < s.length(); i++){
            if(s.charAt(i) == t.charAt(0)){
                dp[i][0] = 1;
            }else if(i > 0){
                dp[i][0] = dp[i-1][0];
            }else{
                dp[i][0] = 0;
            }
        }
        for(int i = 1; i < s.length(); i++) {
            for(int j = 1; j < t.length(); j++) {
                if(s.charAt(i) == t.charAt(j)){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else{
                    dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]);
                }
            }
        }
        if(dp[s.length()-1][t.length()-1] == s.length()) return true;
        return false;

    }
}

二、LeetCode115. 不同的子序列

题目链接:115. 不同的子序列
题目描述:

给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数,结果需要对 109 + 7 取模。

示例 1:

输入:s = "rabbbit", t = "rabbit"
输出3
解释:
如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案rabbbit
rabbbit
rabbbit

示例 2:

输入:s = "babgbag", t = "bag"
输出5
解释:
如下所示, 有 5 种可以从 s 中得到 "bag" 的方案babgbag
babgbag
babgbag
babgbag
babgbag

提示:

  • 1 <= s.length, t.length <= 1000
  • s 和 t 由英文字母组成
算法分析:
定义dp数组及下标含义:

dp[i][j]表示以下标i结束的t字符串在以下标j结尾的s字符串中出现的次数。

递推公式:

当t[i] == s[j]时,dp[i][j]由两个部分组成:

1、用s[j]来匹配,那么dp[i][j] = dp[i-1][j-1];

以当前元素做结尾,那么dp[i][j] = dp[i-1][j-1]; 以j结尾的s字符串中出现的以i结尾的t字符串的次数就等于以j-1结尾的s字符串中出现的以i-1结尾的t字符串出现的次数。

2、不用s[j]来匹配,那么dp[i][j] = dp[i][j-1];

所以dp[i][j] = dp[i-1][j-1] + dp[i][j-1],注意这里是两种情况都可以不是只能选一种,所以总情况要加起来。

例如:s ="bagg",t ="bag";

s[3]和t[2]是相等的,s可以用s[4]来匹配,也即t由s[0]s[1]s[3]组成,

也可以不用s[4]来匹配,即t由s[0]s[1]s[2]组成。

所以当t[i] == s[j] 时,dp[i][j] = dp[i-1][j-1]+dp[i][j-1];

当t[i] != s[j]时,dp[i][j]只有一部分组成:

那就是不用s[j]匹配,即dp[i][j]=dp[i][j-1];

初始化:

根据递推公式我们需要初始化第一行dp[0][j],也就是s当中出现的t[0]元素的个数。

        for(int j = 0; j < s.length(); j++){//dp[0][j]表示以j结尾的s字符串当中t[0]出现的个数
            if(t.charAt(0) != s.charAt(j)){
                if(j == 0) dp[0][j] = 0;
                else dp[0][j] = dp[0][j-1];
            }else{
                if(j == 0) dp[0][j] = 1;
                else dp[0][j] = dp[0][j-1] + 1;
            }
        }
遍历顺序:

先遍历字符串t在遍历字符串s或先遍历s再遍历t都可以,不过都须从前往后遍历。

打印dp数组进行验证。

代码如下:

class Solution {
    public int numDistinct(String s, String t) {
        int[][] dp = new int[t.length()][s.length()];//dp[i][j]表示以下标j结尾的s字符串当中出现的以i结尾的t字符串的个数
        for(int j = 0; j < s.length(); j++){//初始化第一行
            if(t.charAt(0) != s.charAt(j)){
                if(j == 0) dp[0][j] = 0;
                else dp[0][j] = dp[0][j-1];
            }else{
                if(j == 0) dp[0][j] = 1;
                else dp[0][j] = dp[0][j-1] + 1;
            }
        }
        for(int j = 1; j < s.length(); j++) {
            for(int i = 1; i < t.length(); i++) {
                if(t.charAt(i) == s.charAt(j)){
                    dp[i][j] = dp[i-1][j-1] + dp[i][j-1];
                }else{
                    dp[i][j] = dp[i][j-1];
                }
            }
        }
        // for(int i = 0; i < t.length(); i++) {
        //     for(int j = 0; j < s.length(); j++) {
        //        System.out.print(dp[i][j] + " ");
        //     }
        //     System.out.println();
        // }
        return dp[t.length()-1][s.length()-1];

    }
}

总结

第二题还是比较困难的,对于dp[i][j]的递推公式不容易想到。

你可能感兴趣的:(Java算法题解,算法,leetcode,动态规划,java)