LeetCode-Day4-题目:940. 不同的子序列 II(动态规划)【困难】

给定一个字符串 s,计算 s 的 不同非空子序列 的个数。因为结果可能很大,所以返回答案需要对 10^9 + 7 取余 。
字符串的 子序列 是经由原字符串删除一些(也可能不删除)字符但不改变剩余字符相对位置的一个新字符串。
例如,“ace” 是 “abcde” 的一个子序列,但 “aec” 不是。

示例 1:
输入:s = “abc”
输出:7
解释:7 个不同的子序列分别是 “a”, “b”, “c”, “ab”, “ac”, “bc”, 以及 “abc”。

示例 2:
输入:s = “aba”
输出:6
解释:6 个不同的子序列分别是 “a”, “b”, “ab”, “ba”, “aa” 以及 “aba”。

示例 3:
输入:s = “aaa”
输出:3
解释:3 个不同的子序列分别是 “a”, “aa” 以及 “aaa”。

关键字:动态规划
代码:

public int distinctSubseqII(String s) {
        final int mod = 1000000007;
        //存储每个字母最后出现的位置,-1表示还未出现
        int[] last = new int[26];
        Arrays.fill(last,-1);

        int len = s.length();
        //每个字母初始化出现次数为1
        int[] f = new int[len];
        Arrays.fill(f,1);
        for(int i=0;i<len;i++){
            for(int j=0;j<26;j++){
                if(last[j]!=-1){
                    f[i] = (f[i] + f[last[j]])%mod;
                }
            }
            last[s.charAt(i) - 'a'] = i;
        }

        int res = 0;
        for(int i=0;i<26;i++){
            if(last[i]!=-1)
                res = (res+f[last[i]])%mod ;
        }
        return res;
    }

思路:

1.采用动态规划的思想来解答,即求以字符串s中每个字符结尾的所有字符串个数
2.整体细分为局部的思想,将问题进行拆分:假定以第j个字符结尾的字符串总个数用 f[j] 表示,那么求得 f[j] 的结果分为以下的情况:
(1)如果只包含第j个字符,那么就是一个情况,即它自己;
(2)如果字符的个数至少有两个,则f[j] = f[j-1] +1;表示求得第j-1个字符结尾的所有字符串个数加上第j个字符表示的字符串,且第j-1个字符可以为前面所有字符中的任意一个,即:
f[j] = f[j-1]+f[j-2]+f[j-3]+…+f[2]+f[1]+f[0] + 1;
3.用一个数组 last[i] 来表示所有26个字符最后出现的位置,f[i] 表示以该字符结尾的所有字符串个数,初始化将last[]数组所有位置为-1,即都还未出现;将f[]数组初始化位置都为1,即表示以单个字符成串的1种情况。
4.下面进行遍历:
(1)遍历字符串s,对各个位置的字符进行标记状态,记录最后出现的位置last[i];
(2)如果某个字符还未出现,记录当前位置i到last数组中;如果已出现,计算得到f[i],存入数组,同时记录当前字符最后出现的位置
(3)对字符串统计数组进行求和,将last[i]不为-1的所有值累加,即求得字符串s所有不同子序列的个数。

优化方案:

public int distinctSubseqII(String s) {
        final int MOD = 1000000007;
        int[] g = new int[26];
        int n = s.length();
        for (int i = 0; i < n; ++i) {
            int total = 1;
            for (int j = 0; j < 26; ++j) {
                total = (total + g[j]) % MOD;
            }
            g[s.charAt(i) - 'a'] = total;
        }

        int ans = 0;
        for (int i = 0; i < 26; ++i) {
            ans = (ans + g[i]) % MOD;
        }
        return ans;
    }

思路:

1)去掉初始化两个数组的过程,直接只定义一个大小为26的数组来记录所有以该字符结尾的所有字符串个数,初始化都为0;
2)以当前位置字符为结尾的字符串个数用g[i]表示,计算思路还是和上面一样,以该字符i前面所有字符为结尾的字符串个数总和,这里优化思路为:按照遍历字符串s的顺序,可以保证以数组g[]里存储的各个字符为结尾个数是在当前字符之前发生的,即可以直接遍历g数组,将所有个数求和就是我menu需要的结果。

for (int j = 0; j < 26; ++j) {
	total = (total + g[j]) % MOD;
}

3)最后遍历g数组,值求和便是字符串s所有不同子序列的个数。

总结

1)动态规划的思想,将整体问题拆分为小问题去解决,比如当前例子为求字符串的子序列,便可以转换为求字符串中每个字符结尾的所有字符串个数的和。
2)用数组记录当前字符的状态,然后根据推导的状态转换方程,遍历求得结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值