一、环境说明
- 本文是 LeetCode 940题 : 不同的子序列 II,使用c语言实现
- 模拟动态规划
- 测试环境:Visual Studio 2019
二、代码展示
const int mod = 1e9 + 7;
int distinctSubseqII(char * s) {
int g[26]={0};
int total = 0,i = 0;
while(*(s+i)){
int ch = s[i] - 'a';
int pre = g[ch];
g[ch] = (total + 1) % mod;
total = ((total + g[ch] - pre) % mod + mod) % mod;
i++;
}
return total;
}
三、思路分析
- 本题的字符串s只包含26个小写字母的,最大长度2000,求它的子序列数量。
- 对于组合数,有一些数学性质需要分析。
- 性质①,对于一个字符串,比如ab,在其后插入一个字符c,变成abc。以c结尾的组合数,和原字符串ab的组合数有关。
- 具体关系如下:ab可能的组合有 a、b、ab,一共3个组合,加入c之后,可能的组合有 ac、bc、abc、c。可以发现,插入c之后,以c结尾的组合的个数相当于原字符串的组合数+1。这是因为ab一共3种组合,将c分别拼接到3种组合尾部,是3种可能,c单独作为组合,是1种。
- 性质②,对于一个字符串,比如abc,它的组合总数是 以a结尾的组合+以b结尾的组合+以c结尾的组合= a + (ab + b) + (ac + abc +bc +c)=1+2+4=7种。字符串的组合总数就是以各个字符结尾的组合之和。
- 性质③,对于一个字符串,比如ab,在其后插入一个字符a,变成aba。出现重复字符的组合数,只与最后出现的重复字符有关。
- 具体分析如下:对于ab的组合,我们已经分析过有3种组合。而aba,以最后一个a结尾的组合,有一些组合和之前的a重复。考虑剔除重复项,很简单,我们只考虑以最后一个a为结尾的组合,aa、ba、aba、a,我们发现,它包含了之前的a的所有组合。这个性质大家可以思考,是不是这样。其实是这样,我们只用考虑以最后一个a结尾的组合,就能得到所有以a结尾的组合了。
- 这题我们使用性质①②③,就可以做题了。
- 我们先创建g[26]保存26个字母最后出现的位置。再创建一个total存组合的总数。
- 当前字符记作ch,这里我们再创建一个pre,存上一个重复字符的组合数。根据性质①以当前字符结尾的组合数=total+1,然后total变成total +(g[ch] - pre),或者说,组合总数=(之前的组合总数+(以当前字符结尾的组合数-上一个重复字符的组合数))。
- 重复上述操作,遍历整个字符串,最后的total即为所求。
四、代码分析
五、AC
六、复杂度分析
- 时间复杂度:O(n) ,n是字符串s的长度,O(n)是一次遍历字符串的时间复杂度。
- 空间复杂度:O(|C|),|C|对应26个小写字母,|C|=26,数组g[26]的空间复杂度是O(|C|)。