题目
思路
这题在没想到的地方给了我巨大的困扰,先来说说思路,就是从头到尾递推,如果字符串中的每个字符都不一样,例如:abc
"a" a
"ab" a b ab
"abc" a b c ab ac bc abc
很容易看出,加入第 i 位以后的总的子序列数量就是前一位的2倍 + 1, 新增的就是前一位时所有的子序列再加上新增的,以及其本身。
但只有26个小写字母而字符串长度最大为2000,那不可避免的会出现重复字符,那递推过程中遇到了重复的情况就需要减去重复的部分,例如:acc
"a" a
"ac" a c ac
"acc" a c c ac ac cc acc
a c ac cc acc
可以看出需要减去的重复部分就是,前一次重复字符出现时再前一位的子序列量 + 1,减去的是同时都以该字符结尾的部分。用上面的例子来说就是,第1位出现了字母c,相比于第一位来说新增了c和ac两项,而这两项就是我们需要减去的部分。换成公式就是
在实现时只需要在递推的过程中记录下每个字母最新出现的位置,然后在重复时减去相应位置的子序列数量就行了。这样解法的时间复杂度为O(n)
那么代码如下
class Solution {
public:
int distinctSubseqII(string s) {
int flag[26];
for (int i = 0; i < 26; i ++) {
flag[i] = -1;
}
int subseq[2001];
int mod = 1000000007;
subseq[0] = 1;
flag[s[0] - 'a'] = 0;
for (int i = 1; i < s.length(); i++) {
if (flag[s[i] - 'a'] == -1 ) {
subseq[i] = ((subseq[i-1] * 2)%mod + 1 + mod)%mod;
} else if (flag[s[i] - 'a'] == 0){
subseq[i] = ((subseq[i-1] * 2)%mod + mod)%mod;
} else {
subseq[i] = ((subseq[i-1] * 2)%mod - subseq[flag[s[i] - 'a'] - 1] + mod)%mod;
}
flag[s[i] - 'a'] = i;
}
return subseq[s.length()-1]%mod;
}
};
提交结果
反思
今天的代码看起来不是很美,而其中有反复提交了数遍的挣扎。
一、因为第0项不存在前一项,所以在判断重复时要单独判断,在写时出现了几次,超出范围的情况。
二、可能是没睡好,提交了几次代码都只对了一半,最后发现是取余数少写了一个0,现在想来应该仔细检查再提交。
三、提交时发现结果为负,可以想到是前一项乘2取余数以后减去前一项结果导致的,所以在最后取余前多加一次余数以此避免负数的出现。这个小机巧应该可以在需要最终取余数的题目中经常使用。
四、结果还是有可以优化的地方,主要是细节优化。不过这题主要还是找出递推的规律,之后就很简单,同样可以出一道数组的子序列的题目也是类似的解法。