题目来源
题目描述
class Solution {
public:
int distinctSubseqII(string s) {
}
};
题目解析
统计必须以a~z结尾的子序列的个数
class Solution {
public:
int distinctSubseqII(string s) {
std::vector<long> dp(26, 0);
int mod = 1e9 + 7;
for(auto &ch : s){
dp[ch - 'a'] = accumulate(dp.begin(), dp.end(), 1l) % mod;
}
return accumulate(dp.begin(),dp.end(),0L)%mod;//求和
}
};
动态规划+哈希表
从左到右尝试模型
如果序列中没有重复的单词
每一位,要么选,要么不选。那么长度为N的序列的子序列个数,就是 2 N 2^N 2N
如果用动态规划来求解的话,令 d p [ i ] dp[i] dp[i]为 s [ 1... i ] s[1...i] s[1...i]的子序列的个数(包含空字符串)那么:
- 状态转移方程为 d p [ i + 1 ] = 2 ∗ d p [ i ] dp[i+1] = 2 * dp[i] dp[i+1]=2∗dp[i]
- 其中 d p [ 0 ] = 1 dp[0] = 1 dp[0]=1, 对于空字符串,其子序列当然也只有一个,就是空字符串。
如果序列中有重复子串呢?
- 比如 “abcbc” 中 “ab” 即可以是 s[0] + s[1] 也可以是 s[0] + s[3]。
- 我们沿用动态规划的思想,这个时候s[i+1]取不取,产生的所有子序列包含两部分。
- 一部分是s[i+1]不取,那么这部分子序列和s[i]对应的子序列是一样的。
- 另一部分s[i+1]取,那么这部分子序列里会有一部分和s[i]的子序列重合,我们需要找出来这一部分扣除即可。
- 如果s[i+1]之前从来没出现过,那么显然不会有重合的部分。
- 如果s[i+1]出现过,上一次出现的位置假设是last,那么dp[last]其实就是重叠的部分。因为他们的最后一位都是一样的。我们将这一部分减去。
- 因此,最终的状态转移方程如下: d p [ i + 1 ] = d p [ i ] ∗ 2 − d p [ l a s t [ s [ i ] ] ] dp[i+1] = dp[i] * 2 - dp[last[s[i]]] dp[i+1]=dp[i]∗2−dp[last[s[i]]]
class Solution {
public:
int distinctSubseqII(string s) {
int n = s.size();
int MOD = 1000000007;
if(n == 0){
return 0;
}
std::vector<int> dp(n + 1);
std::vector<int> last(26, -1);
dp[0] = 0;
for (int i = 0; i < n; ++i) {
dp[i + 1] = dp[i] * 2 % MOD;
if(last[s[i] - 'a'] >= 0){
dp[i + 1] -= dp[last[s[i] - 'a']];
}
dp[i + 1] %= MOD;
last[s[i] - 'a'] = i;
}
// 最终去掉空字符串
return (dp[n] - 1 + MOD) % MOD;
}
};