一【题目类别】
- 字符串
二【题目难度】
- 困难
三【题目编号】
- 115.不同的子序列
四【题目描述】
- 给定一个字符串s和一个字符串 t ,计算在s的子序列中t出现的个数。
- 字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)
- 题目数据保证答案符合32位带符号整数范围。
五【题目示例】
- 示例 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
^^^
六【题目提示】
- 0 <= s.length, t.length <= 1000
- s 和 t 由英文字母组成
七【解题思路】
- 深度优先搜索+记忆化递归/回溯
- 因为这两天做了回溯的题,首先想到的就是回溯,类似记忆化递归,需要一个二维数组记忆s的某个位置和t的某个位置开始的匹配个数,递归时只要匹配当前值是否相等,如果当前位置的元素匹配,继续匹配下一个,如果当前位置元素不匹配,s跳过当前位置元素,用s的下一个元素和t的当前元素继续匹配,如果t的指针到了结尾说明匹配到了一个,如果s指针已经指向了结尾还没匹配到,说明从当前位置开始不存在匹配的子串,直接返回,如果s的某个位置和t的某个位置开始已经匹配到了,就直接返回,可以看作回溯
八【时间频度】
- 时间复杂度:时间复杂度一般为常数,递归树的高度 O ( H ) O(H) O(H)
九【代码实现】
- Java语言版
package String;
public class p115_DistinctSubsequences {
public static void main(String[] args) {
String s = "babgbag";
String t = "bag";
int res = numDistinct(s, t);
System.out.println("res = " + res);
}
public static int numDistinct(String s, String t) {
// 求出s和t的长度
int s_len = s.length();
int t_len = t.length();
// 创建对应的二维数组
int dp[][] = new int[s_len + 1][t_len + 1]; // 这里+1的作用是考虑到t和s为0的情况
// 将每一行的最后一位置为1,因为当t为空时,只能在s中选中一个,而当s为空时,一个也选不中,按理说应该把每列的最后一个置为 0,但是二维数组默认就是0,这里只需要修改每一行的最后一位
for (int i = 0; i <= s_len; i++) {
dp[i][t_len] = 1;
}
// 从这里开始,倒着进行循环,从t字符串的后面开始,t每次增加一个字母,因为是从s里面找t,所以t的循环在外面
for (int t_i = t_len - 1; t_i >= 0; t_i--) {
// 倒着进行循环,从s字符串的后面开始,s每次增加一个字母
for (int s_i = s_len - 1; s_i >= 0; s_i--) {
// 如果两个字母相等
if (t.charAt(t_i) == s.charAt(s_i)) {
// 在相等的情况下,可以选择s中的字母,跳过s中的,也跳过t中的;也可以不选择s中的字母,跳过s中的,不跳过t中的
dp[s_i][t_i] = dp[s_i + 1][t_i + 1] + dp[s_i + 1][t_i];
}
// 如果两个字母不相等
else {
// 不选择s中的字母,跳过s中的,不跳过t中的
dp[s_i][t_i] = dp[s_i + 1][t_i];
}
}
}
// 因为是从后向前循环的,所以返回第一行第一列的值
return dp[0][0];
}
}
- C语言版
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int numDistinct(char * s, char * t)
{
/*求出s和t的长度*/
int s_len = strlen(s);
int t_len = strlen(t);
/*创建对应的w二维数组,并开辟空间,这里要用long,要不输入z字符串太长会出错,这里+1的作用是考虑到t和s为0的情况*/
long dp[0 + 1][0 + 1]; /*因为c语言不允许使用没定义的参数,所以这里写成固定的,正常应该是long dp[s_len + 1][t_len + 1];*/
memset(dp, 0, sizeof(dp));
/*将每一行的最后一位置为1,因为当t为空时,只能在s中选中一个,而当s为空时,一个也选不中,按理说应该把每列的最后一个置为 0,但是二维数组默认就是0,这里只需要修改每一行的最后一位*/
for (int i = 0; i <= s_len; i++)
{
dp[i][t_len] = 1;
}
/*从这里开始,倒着进行循环,从t字符串的后面开始,t每次增加一个字母,因为是从s里面找t,所以t的循环在外面*/
for (int t_i = t_len - 1; t_i >= 0; t_i--)
{
/*倒着进行循环,从s字符串的后面开始,s每次增加一个字母*/
for (int s_i = s_len - 1; s_i >= 0; s_i--)
{
/*如果两个字母相等*/
if (t[t_i] == s[s_i])
{
/*在相等的情况下,可以选择s中的字母,跳过s中的,也跳过t中的;也可以不选择s中的字母,跳过s中的,不跳过t中的*/
dp[s_i][t_i] = dp[s_i + 1][t_i + 1] + dp[s_i + 1][t_i];
}
/*如果两个字母不相等*/
else
{
/*不选择s中的字母,跳过s中的,不跳过t中的*/
dp[s_i][t_i] = dp[s_i + 1][t_i];
}
}
}
/*因为是从后向前循环的,所以返回第一行第一列的值*/
return dp[0][0];
}
/*主函数省略*/
十【提交结果】
-
Java语言版
-
C语言版