如何根据已有模型去思考dp问题

82 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情

在一些题目中,我们需要去用到动态规划的思想来解决一类问题;

可是,动态规划凭空想出来是很难的,但是我们可以学习很多基础模型,然后根据已有模型去思考和解决新的问题,这就是把知识点给泛化了!

下面我们来看一个问题吧!

题目

给定一个字符串 s,计算 s 的 不同非空子序列 的个数。因为结果可能很大,所以返回答案需要对 10^9 + 7 取余 。

字符串的 子序列 是经由原字符串删除一些(也可能不删除)字符但不改变剩余字符相对位置的一个新字符串。

  • 例如,"ace" 是 "abcde" 的一个子序列,但 "aec" 不是。

题目理解


本题求的是一个不同非空子序列的个数,比如 abc => 有七个不相同的子序列,分别为a,b,c,ab,ac,abca, b, c, ab, ac, abc

下面,我们来思考一下,如何来做这一个问题!


首先, 我们要明确,我们求的是什么东西,我们求的是非空子序列的个数,且要求子序列是不重复

那么,允许重复的子序列是什么情况呢?

这个好计算,就是n!n!个子序列,我们需要去枚举这么多个子序列,那么我们肯定是不能够枚举的

我们观察一下里面所包含的重复状态的值:

例如 abcde:

第一轮:a第二轮:aab,b第三轮:a,ab,bac,abc,bc,c第四轮:a,ab,b,ac,abc,bc,cad,abd,bd,acd,abcd,bcd,cd,d....那么,聪明的你应该是可以发现,我们当前这个字符,添加到原来所以的字符末尾即可以形成新的字符集第一轮: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第二轮:aab,b第三轮:a,ab,baa,aba,ba第四轮:a,ab,b,aa,aba,baaaa,abaa,baa...注意到,第三轮中,丢失了一个a第四轮中丢失了a,aa,aba,ba他们有什么特点呢?除了a之外,aaababa的共同特性在于,和上一轮的重复了!那么,就意味着,有ka,对于这ka前面的的这一些字母的集合,只会保留一份所以,对于当前a来说,其实累加的是上一轮所以字母结尾计数所带来的值我把他分一下类,你大概就明白了aa,aba,ba,分为以ab结尾的字符aab,b那么,下一个a的累加就会累加上a类字符和b类字符,同时,自身也是一个字符所以要再加1这样,我们就变成了a,aa,aba,baab,b然后,我们再次如法炮制a累加上a类字符和b类字符,同时,自身可以自成一体a,aa,aaa,abaa,baa,aba,baab,b现在,我们就大概可以写出我们的状态方程了f[i]=f[i1]先将原来的所有的状态全部转移过来f[i][nov]=1+i=azf[i1][i],然后去累加上一个状态所以的值第一轮: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 + \sum_{i=a}^z f[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
    }
}