【每日一题】940. 不同的子序列 II 2022/10/14

题目

940. 不同的子序列 II

思路

这题在没想到的地方给了我巨大的困扰,先来说说思路,就是从头到尾递推,如果字符串中的每个字符都不一样,例如: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两项,而这两项就是我们需要减去的部分。换成公式就是

ans[i] = ans[i-1] * 2 - ans[pre[s[i]-'a'] - 1]

在实现时只需要在递推的过程中记录下每个字母最新出现的位置,然后在重复时减去相应位置的子序列数量就行了。这样解法的时间复杂度为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取余数以后减去前一项结果导致的,所以在最后取余前多加一次余数以此避免负数的出现。这个小机巧应该可以在需要最终取余数的题目中经常使用。

四、结果还是有可以优化的地方,主要是细节优化。不过这题主要还是找出递推的规律,之后就很简单,同样可以出一道数组的子序列的题目也是类似的解法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今夜大疯小宇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值