115_不同的子序列
[[toc]]
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
思路:
我们需要计算s
中有多少个t
,实际上就是穷举,那么我们需要考虑能不能把愿问题分解为规模更小的子问题,然后再根据子问题推导出愿问题的结果。
首先,我们可以定义这样一个dp
函数: 1
2// 定义:s[i..] 的子序列中 t[j..]出现的次数为 dp(s,i,t,j)
int dp(string s, int i, string t, int j)dp(s, 0, t, 0)
base case
为: * 1. 全部匹配完成,j == t.size()
* 2. s[i..]比t[j..]还短,那么必然不可能匹配
至此,我们可以写出大概的算法框架: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15int numDecodings(string s, string t) {
return dp(s, 0, t, 0);
}
int dp(string s, int i, string t, int j) {
// base case 1, 全部匹配完成
if(j == t.size()) {
return 1;
}
// base case 2, s[i..]的长度比t[j..]的长度短,匹配失败
if(s.size() - i < t.size() - j) {
return 0;
}
// ...
}
现在我们需要考虑如何将大问题分解为小问题,即如何求出状态转移方程。
有两种思路,第一种就是从t
的角度进行思考,第二种是从s
的角度进行思考。
角度一:从t
的角度
我们的问题是求s[i..]
的所有子序列中t[j..]
出现的次数,那么我们肯定是先判断t[0]
在s
中出现的位置。
假如s[2],s[6]
是字符t[0]
,那么原问题就转化为在s[2..] ,s[6..]
的所有子序列中计算t[1..]
出现的次数。
1 | int dp(string s, int i, string t, int j) { |
最后我们只需要加上备忘录消除重叠子问题即可。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class Solution {
public:
vector<vector<int> > memo;
int numDistinct(string s, string t) {
memo.resize(s.size(), vector<int>(t.size(), -1));
return dp(s, 0, t, 0);
}
// 定义:s[i...] 的子序列中 t[j...]出现的次数为dp(s, i, t, j)
int dp(string s, int i, string t, int j) {
// base case 1
if(j == t.size()) {
return 1;
}
// base case 2
if(s.size() - i < t.size() - j) {
return 0;
}
if(memo[i][j] != -1) {
return memo[i][j];
}
int res = 0;
// 状态转移方程
// 在 s[i..]中寻找k,使得s[k] == t[j]
for(int k = i; k < s.size(); k++) {
if(s[k] == t[j]) {
res += dp(s, k + 1, t, j + 1);
}
}
memo[i][j] = res;
return res;
}
};
时间复杂度:O(m*n) * O(m)
m表示s的长度,n为t的长度。 子问题的个数 * 函数本身的时间复杂度
角度二:从s
的监督
我们的问题是计算s[0..]
的所有子序列中t[0..]
出现的次数,那么我们可以先看下s[0]
是否能匹配t[0]
,
如果不匹配,那么愿问题就可以转化为计算s[1..]
的所有子序列中t[0..]
出现的次数。
如果匹配,那么有以下两种情况,这两种情况是累加的关系: * 1.
让s[0]
匹配t[0]
,那么原问题转化为在s[1..]
的所有子序列中计算t[1..]
出现的次数。
* 2.
不让s[0]
匹配t[0]
,那么原问题转化为在s[1..]
的所有子序列中计算t[0..]
出现的次数。
那么状态转移方程为: 1
2
3
4
5
6
7
8
9int dp(string s, int i, string t, int j) {
if(s[i] == t[j]) {
// 匹配,两种情况
return dp(s, i + 1, t, j + 1) + dp(s, i + 1, t, j);
}else {
// 不匹配
return dp(s, i + 1, t, j);
}
}
加上备忘录,代码如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class Solution {
public:
vector<vector<int> > memo;
int numDistinct(string s, string t) {
memo.resize(s.size(), vector<int>(t.size() , -1));
return dp(s, 0, t, 0);
}
int dp(string s, int i, string t, int j) {
// base case 1
if(j == t.size()) {
return 1;
}
// base case 2
if(s.size() - i < t.size() - j) {
return 0;
}
if(memo[i][j] != -1) {
return memo[i][j];
}
int res = 0;
// 状态转移方程
if(s[i] == t[j]) {
res += dp(s, i + 1, t, j + 1) + dp(s, i + 1, t, j);
}else {
res += dp(s, i + 1, t, j);
}
// 将结果存入备忘录
memo[i][j] = res;
return res;
}
};
时间复杂度为:O(mn)