392.判断子序列 115.不同的子序列
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
两个字符串都只由小写字符组成。
思路
思路:动态规划 和最长公共子序列思路解法类似 区别在于递推公式 本题 如果删元素一定是字符串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.不同的子序列
给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)
题目数据保证答案符合 32 位带符号整数范围。
提示:
- 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()];
}