给定字符串 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[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的遍历顺序无所谓,不过都需从前往后遍历。
代码如下:
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;
}
}
给你两个字符串 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[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都可以,不过都须从前往后遍历。
代码如下:
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]的递推公式不容易想到。