持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情
在一些题目中,我们需要去用到动态规划的思想来解决一类问题;
可是,动态规划凭空想出来是很难的,但是我们可以学习很多基础模型,然后根据已有模型去思考和解决新的问题,这就是把知识点给泛化了!
下面我们来看一个问题吧!
题目
给定一个字符串 s,计算 s 的 不同非空子序列 的个数。因为结果可能很大,所以返回答案需要对 10^9 + 7 取余 。
字符串的 子序列 是经由原字符串删除一些(也可能不删除)字符但不改变剩余字符相对位置的一个新字符串。
- 例如,
"ace" 是 "abcde" 的一个子序列,但 "aec" 不是。
题目理解
本题求的是一个不同非空子序列的个数,比如 abc => 有七个不相同的子序列,分别为a,b,c,ab,ac,abc
下面,我们来思考一下,如何来做这一个问题!
首先, 我们要明确,我们求的是什么东西,我们求的是非空子序列的个数,且要求子序列是不重复的
那么,允许重复的子序列是什么情况呢?
这个好计算,就是n!个子序列,我们需要去枚举这么多个子序列,那么我们肯定是不能够枚举的
我们观察一下里面所包含的重复状态的值:
例如 abcde:
第一轮:a第二轮:a∣ab,b第三轮:a,ab,b∣ac,abc,bc,c第四轮:a,ab,b,ac,abc,bc,c∣ad,abd,bd,acd,abcd,bcd,cd,d....那么,聪明的你应该是可以发现,我们当前这个字符,添加到原来所以的字符末尾即可以形成新的字符集
那么,遇见重复的情况呢?
例如 abaa
第一轮:a第二轮:a∣ab,b第三轮:a,ab,b∣aa,aba,ba第四轮:a,ab,b,aa,aba,ba∣aaa,abaa,baa...注意到,第三轮中,丢失了一个a第四轮中丢失了a,aa,aba,ba等他们有什么特点呢?除了a之外,aa,aba,ba的共同特性在于,和上一轮的重复了!那么,就意味着,有k个a,对于这k个a前面的的这一些字母的集合,只会保留一份所以,对于当前a来说,其实累加的是上一轮所以字母结尾计数所带来的值我把他分一下类,你大概就明白了把aa,aba,ba,分为以a和b结尾的字符a∣ab,b那么,下一个a的累加就会累加上a类字符和b类字符,同时,自身也是一个字符所以要再加1这样,我们就变成了a,aa,aba,ba∣ab,b然后,我们再次如法炮制a累加上a类字符和b类字符,同时,自身可以自成一体a,aa,aaa,abaa,baa,aba,ba∣ab,b现在,我们就大概可以写出我们的状态方程了f[i]=f[i−1]先将原来的所有的状态全部转移过来f[i][nov]=1+i=a∑zf[i−1][i],然后去累加上一个状态所以的值
代码实现
我们来看一下代码实现
impl Solution {
pub fn distinct_subseq_ii(s: String) -> i32 {
let (n, MOD) = (s.len(), 1e9 as i32 + 7);
let mut f: Vec<Vec<i32>> = vec![vec![0; 26]; n];
let mut ans = 0;
let to_idx = |v| (v - b'a') as usize;
for (i, v) in s.bytes().enumerate() {
if i == 0 {
f[i][to_idx(v)] = 1;
} else {
f[i] = f[i - 1].clone();
f[i][to_idx(v)] = 1;
for j in 0..26 as usize {
f[i][to_idx(v)] += f[i - 1][j];
f[i][to_idx(v)] %= MOD;
}
}
}
for x in &f[n - 1] {
ans += x;
ans %= MOD;
}
ans
}
}