给定一个字符串 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)用数组记录当前字符的状态,然后根据推导的状态转换方程,遍历求得结果。