392.判断子序列 115.不同的子序列

392.判断子序列 115.不同的子序列

392.判断子序列

力扣题目链接(opens new window)

给定字符串 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

两个字符串都只由小写字符组成。

思路

思路:动态规划
和最长公共子序列思路解法类似
区别在于递推公式
本题 如果删元素一定是字符串t,而 1143.最长公共子序列 是两个字符串都可以删元素。
即:dp[i][j] = dp[i][j - 1];
时间复杂度O(N^2)
空间复杂度O(N^2)

代码如下

public boolean isSubsequence(String s, String t) {
    if (s == null || t == null)
        return false;
    if (s.equals("") && !t.equals(""))// 测试用例中 s=="" ,t != "",结果为true
        return true;
    if(s.equals("") && t.equals("")){
        return true;
    }
    if(!s.equals("") && t.equals("")){
        return false;
    }
    char[] chars = s.toCharArray();
    char[] chart = t.toCharArray();
    int[][] dp = new int[s.length()][t.length()];// 定义dp数组并初始化
    for (int i = 0; i < s.length(); i++) {
        if (chars[i] == chart[0]) {
            dp[i][0] = 1;
        }
        if (i > 0 && dp[i - 1][0] == 1) {
            dp[i][0] = 1;
        }
    }
    for (int j = 0; j < t.length(); j++) {
        if (chart[j] == chars[0]) {
            dp[0][j] = 1;
        }
        if (j > 0 && dp[0][j - 1] == 1) {
            dp[0][j] = 1;
        }
    }


    for (int i = 1; i < s.length(); i++) {
        for (int j = 1; j < t.length(); j++) {
            if (chars[i] == chart[j]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = dp[i][j] = dp[i][j - 1];
            }
        }
    }
    return dp[s.length() - 1][t.length() - 1] == s.length();
}

优化

本题目属于编辑距离类题目。只有删除操作,且只会删除t上元素
二刷时采用另一种定义dp数组的方式、
dp[i][j]表示以s[i-1]和t[i-1]为结尾字符串中,最长公共子序列长度
采用这种定义dp数组方式,可简化初始化dp数组流程

代码如下

public boolean isSubsequence(String s, String t) {
    if (s == null || t == null)
        return false;
    int[][] dp = new int[s.length() + 1][t.length() + 1];
    for (int i = 1; i <= s.length(); i++) {
        for (int j = 1; j <= t.length(); j++) {
            if (s.charAt(i - 1) == t.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = dp[i][j - 1];
            }
        }
    }
    return dp[s.length()][t.length()] == s.length();
}

115.不同的子序列

力扣题目链接(opens new window)

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)

题目数据保证答案符合 32 位带符号整数范围。

392.判断子序列 115.不同的子序列_第1张图片

提示:

  • 0 <= s.length, t.length <= 1000
  • s 和 t 由英文字母组成

思路

思路:动态规划
本题目是编辑距离类问题,只涉及到删除元素,属于较为简单的编辑距离类
但相比判断子序列,难度有所提升
因为题目要求计算 s 的子序列中 t 出现的个数
t是不需要增删元素,只需要对s做删除元素操作直至等于t

动态规划五部曲
1.定义dp数组以及下标含义
定义二维dp数组。dp[i][j]表示以字符s.chatAt(i)为结尾字符串子序列中,出现以t.chatAt(j)字符串的个数
2。定义递推公式
当s.chatAt(i) == t.chatAt(j).dp[i][j]可以有两部分组成。
情况一 :s的子序列使用s.chatAt(i)匹配
举例s:bagg  t:bag。此时s.chatAt(3) == t.chatAt(2)。使用s.chatAt(3)来匹配。那么就从s(bag)中找出t(ba)子序列个数即可
递推公式为dp[i][j] = dp[i-1][j-1]

情况二:s的子序列不使用s.chatAt(i)匹配
举例s:bagg  t:bag。此时s.chatAt(3) == t.chatAt(2)。不使用s.chatAt(3)来匹配。那么就从s(bag)中找出t(bag)子序列个数即可
dp[i][j] = dp[i-1][j] 就是模拟在s中删除s[i]元素.因为题目要求计算 s 的子序列中 t 出现的个数,t是不需要增删元素,只需要对s做删除元素操作直至等于t,只考虑删除s元素即可

所以当s.chatAt(i) == t.chatAt(j)。递推公式为 dp[i][j] = dp[i-1][j] + dp[i-1][j-1]
当s.chatAt(i) != t.chatAt(j).dp[i][j]为 dp[i - 1][j];
3.初始化dp数组
老规矩,对二维dp数组的第一行和第一列分析
第一列中若存在s.chatAt(i) == t.chatAt(0),则dp[i][0]包含子序列个数应加1
若s.chatAt(i) != t.chatAt(0) 则dp[i][0]包含子序列个数等于dp[i-1][0]
第一行不需要初始化了
因为第一行中除了dp[0][0]外,其余元素中t 长度大于等于 s。t不可能为s子序列,所以初始化为0即可
至于dp[0][0]在初始化第一列时就赋值了
4.遍历顺序
有递推公式可知
从小到大遍历即可
5.举例推导dp数组
时间复杂度O(N^2)
空间复杂度O(N^2)

代码如下

public static int numDistinct(String s, String t) {
    // 处理边界条件
    if (s.equals("") && t.equals(""))
        return 1;
    if (s.equals("") && !t.equals(""))
        return 0;
    if (!s.equals("") && t.equals(""))
        return 1;

    int[][] dp = new int[s.length()][t.length()];
    for (int i = 0; i < s.length(); i++) {// 初始化第一列
        if (s.charAt(i) == t.charAt(0) && i == 0) {
            dp[0][0] = 1;
        } else if (i > 0) {
            dp[i][0] = dp[i - 1][0];
            if (s.charAt(i) == t.charAt(0)) {
                dp[i][0] = dp[i - 1][0] + 1;
            }
        }
    }
    // 因为第一行中除了dp[0][0]外,其余元素中t 长度大于等于 s。t不可能为s子序列,所以初始化为0即可
    // 至于dp[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] + dp[i - 1][j];
            } else {
                dp[i][j] = dp[i - 1][j];
            }

        }
    }
    return dp[s.length() - 1][t.length() - 1];

}

优化

 优化
 二刷时我采用另一种定义dp数组的定义方式。
 以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]
 此题目属于编辑距离类题目,此类题目均可使用这种定义方式
 该方式的优点:简化初始化dp数组过程
 每次当初始化的时候,都要回顾一下dp[i][j]的定义,不要凭感觉初始化。
 dp[i][0]表示什么呢?
 dp[i][0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。
 那么dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。
再来看dp[0][j],dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。
那么dp[0][j]一定都是0,s如论如何也变成不了t。
最后就要看一个特殊位置了,即:dp[0][0] 应该是多少。
dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t

代码如下

public static int numDistinct(String s, String t) {
    if (s == null || t == null)
        return 0;
    int[][] dp = new int[s.length() + 1][t.length() + 1];
    for (int i = 0; i < s.length(); i++) {
        dp[i][0] = 1;
    }

    for (int i = 1; i <= s.length(); i++) {
        for (int j = 1; j <= t.length(); j++) {
            if (s.charAt(i - 1) == t.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    return dp[s.length()][t.length()];

}

你可能感兴趣的:(leetcode,算法,数据结构,java)