学生的出勤记录II--动态规划和dfs记忆化解决

题目

题目解析(其实重在连续 L 的处理)

dfs记忆化的思路

这道题浏览后,乍一看就觉得是关于多个变量的 dfs 记忆化操作,我也是朝着这个方向走的,但是问题却出在考虑的情况太多。。我把不够成答案的组合也算在内了,实际上我们每次 dfs 只需要枚举可形成答案的组合即可。所以一旦 A 和 L 的选择达到最大上限,则不可再选。(L较为特殊需要连续的)

基本的dfs框架有了,如何记忆化呢?

有了基本dfs框架,那么记忆化就很简单了,我们用 memo[n][2][3]=>memo[i][j][k] 来记录状态,因为这三个状态量固定后,后面的东西均可形成记忆化,表示到了第 i 天我们已经选择了 j 次 A 和 k 次 L ,由于 L 的次数计数是要连续的,所以只要当前选择的不是 L 则 L 计数永远都是0。

dp数组思路

通过三维dp数组进行状态转移:dp[n][2][3]

dp[i][j][k]:

  • 第一维 i 表示当前一共到了第几天。
  • 第二维 j 表示这些天里选择 A 的次数。
  • 第三维 k 表示这些天里连续选择 L 的次数。

状态转移方程:
对于每一天,我们选择 P 、A 、 L 会有不同的状态转移。

  • 当天选择 P 的状态转移方程:dp[i][j][0] = (dp[i][j][0] + dp[i-1][j][k]) % mod,左边是当天选择 P 后所有可能出现的状态,它能转移到上一天的所有状态。
  • 当天选择 A 的状态转移方程:dp[i][1][0] = (dp[i][1][0] + dp[i-1][0][k]) % mod,同样左边也是选择 A 后可能出现的状态,对于 A 由于只能最多选择一次,所以该天状态只能有这一种,而且由于该天状态 A 选择了一次,则上一天只能选择0次了。
  • 当天选择L的状态转移方程:dp[i][j][k] = (dp[i][j][k] + dp[i-1][j][k-1]) % mod,选择 L 后终于可以出现连续的 L 了,所以当天状态为所有可能的状态,而依赖的上一天的状态只能是 k-1 个 L。

注意到这个状态转移的过程是:当天的可能状态推导出可以依赖的上一天的状态。

基本情况:对于 dp[0][][]对于第0天(也就是第一天)的选择,我们每一种选择只可能有一种组合:

   dp[0][0][0] = 1
   dp[0][1][0] = 1
   dp[0][0][1] = 1

解题代码

dfs记忆化

class Solution {
public:
    int checkRecord(int n) {
        const int MOD = 1e9+7;
        int memo[n+1][2][3];memset(memo,0,sizeof(memo));
        function<int(int,int,int)> dfs = [&](int cntA,int cntL,int k){
            if(k==n){
                return 1;
            }
            if(memo[k][cntA][cntL])return memo[k][cntA][cntL];
            int res = 0;
           for(int i=0;i<3;i++){
               //不能再选A了或L的情况进行跳过
                if(cntA>=1&&i==0)
                    continue;
                if(cntL>=2&&i==1)
                    continue;
                int A_t = cntA,L_t;
                if(i==0) {A_t ++;L_t=0;}
                else if(i==1) L_t = cntL+1;
                else L_t = 0;
                res = (res+dfs(A_t,L_t,k+1))%MOD;
            }
          return  memo[k][cntA][cntL] = res;
        };
        return dfs(0,0,0);
    }
    
};

dp数组状态转移

func checkRecord(n int) (ans int) {
    const mod  = 1e9 + 7
    dp := make([][2][3]int, n) // 三个维度分别表示:长度,A 的数量,结尾连续 L 的数量
    dp[0][0][0] = 1
    dp[0][1][0] = 1
    dp[0][0][1] = 1
    for i := 1; i < n; i++ {
// 选择P,只不是选择L的话,L的位置都设为0,因为既然选择了其他的就不可能存在连续的数据
        for j := 0; j <= 1; j++ {
            for k := 0; k <= 2; k++ {
                dp[i][j][0] = (dp[i][j][0] + dp[i-1][j][k]) % mod
            }
        }
        // 选择A,第二维度为0是同理的
        for k := 0; k <= 2; k++ {
            dp[i][1][0] = (dp[i][1][0] + dp[i-1][0][k]) % mod
        }
        // 选择L,这样就可以连续了
        for j := 0; j <= 1; j++ {
            for k := 1; k <= 2; k++ {
                dp[i][j][k] = (dp[i][j][k] + dp[i-1][j][k-1]) % mod
            }
        }
    }
    //加上所有第n-1天的产物
    for j := 0; j <= 1; j++ {
        for k := 0; k <= 2; k++ {
            ans = (ans + dp[n-1][j][k]) % mod
        }
    }
    return ans
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值