力扣 552. 学生出勤记录 II(困难)

题目

可以用字符串表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:

  • ‘A’:Absent,缺勤
  • ‘L’:Late,迟到
  • ‘P’:Present,到场

如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:

  • 按总出勤计,学生缺勤(‘A’)严格 少于两天。
  • 学生不会存在连续 3 天或连续 3 天以上的迟到(‘L’)记录。

给你一个整数 n ,表示出勤记录的长度(次数)。请你返回记录长度为 n 时,可能获得出勤奖励的记录情况 数量 。答案可能很大,所以返回对 109 + 7 取余的结果。
示例 1:

输入:n = 2
输出:8
解释:
有 8 种长度为 2 的记录将被视为可奖励:
"PP" , "AP", "PA", "LP", "PL", "AL", "LA", "LL" 
只有"AA"不会被视为可奖励,因为缺勤次数为 2 次(需要少于 2 次)。

示例 2:

输入:n = 1
输出:3

示例 3:

输入:n = 10101
输出:183236316

提示:

  • 1 < = n < = 1 0 5 1 <= n <= 10^5 1<=n<=105

题解

方法一:动态规划
因为n最大为 1 0 5 10^5 105,所以肯定就不能用回溯法了,这样会超时,因此选择动态规划。
由于可奖励的出勤记录要求缺勤次数少于 2 和连续迟到次数少于 3,因此动态规划的状态由总天数、缺勤次数和结尾连续迟到次数决定。
定义 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示第 i 天存在 j 次缺勤,结尾连续迟到了 k 次,其中 0 ≤ i ≤ n , 0 ≤ j ≤ 1 , 0 ≤ k ≤ 2 0 \leq i \leq n, 0 \leq j \leq 1, 0 \leq k \leq 2 0in,0j1,0k2
当 i = 0时,没有出勤记录,此时边界情况是 d p [ i ] [ j ] [ k ] = 1 dp[i][j][k]=1 dp[i][j][k]=1
1 ≤ i ≤ n 1 \leq i \leq n 1in时, d p [ i ] [ ] [ ] dp[i][][] dp[i][][]的值由 d p [ i − 1 ] [ ] [ ] dp[i-1][][] dp[i1][][]转移得到,计算每个状态的值需要考虑到第 i 天的出勤状况:

  • 如果第 i 天的出勤是 ‘P’,那么和第 i - 1天的出勤记录相比,'A’的数量不变,结尾连续’L’的数量清零,因此对于 0 ≤ j ≤ 1 0 \leq j \leq 1 0j1,有
    d p [ i ] [ j ] [ 0 ] : = d p [ i ] [ j ] [ 0 ] + ∑ k = 0 2 d p [ i − 1 ] [ j ] [ k ] dp[i][j][0] := dp[i][j][0] + \sum_{k = 0}^2dp[i - 1][j][k] dp[i][j][0]:=dp[i][j][0]+k=02dp[i1][j][k]
    比如说对于 d p [ i ] [ 0 ] [ 0 ] = d p [ i − 1 ] [ 0 ] [ 0 ] + d p [ i − 1 ] [ 0 ] [ 1 ] + d p [ i − 1 ] [ 0 ] [ 2 ] dp[i][0][0] = dp[i - 1][0][0] + dp[i - 1][0][1] +dp[i - 1][0][2] dp[i][0][0]=dp[i1][0][0]+dp[i1][0][1]+dp[i1][0][2],表示在过去缺勤次数为0的情况下,可以有0,1,2次连续结尾连续迟到。
  • 如果第 i 天的出勤是 ‘A’,那么和第 i - 1天的出勤记录相比,'A’的数量加1,结尾连续’L’的数量清零,这就要求此前 i - 1天的出勤记录中’A’的数量为0,因此有:
    d p [ i ] [ 1 ] [ 0 ] : = d p [ i ] [ 1 ] [ 0 ] + ∑ k = 0 2 d p [ i − 1 ] [ 0 ] [ k ] dp[i][1][0] := dp[i][1][0] + \sum_{k = 0}^2dp[i - 1][0][k] dp[i][1][0]:=dp[i][1][0]+k=02dp[i1][0][k]
  • 如果第 i 天的出勤是 ‘L’,那么和第 i - 1天的出勤记录相比,'A’的数量不变,结尾连续’L’的数量加1,这就要求此前 i - 1天的出勤记录中结尾连续’L’的数量小于2,也就是第 i 天结尾连续’L’的数量为1或2,因此对于 0 ≤ j ≤ 1 , 1 ≤ k ≤ 2 0 \leq j \leq 1, 1 \leq k \leq 2 0j1,1k2有:
    d p [ i ] [ j ] [ k ] : = d p [ i ] [ j ] [ k ] + d p [ i − 1 ] [ j ] [ k − 1 ] dp[i][j][k] := dp[i][j][k] + dp[i - 1][j][k - 1] dp[i][j][k]:=dp[i][j][k]+dp[i1][j][k1]
    最后计算长度为n的所有可奖励的出勤记录的数量,计算过程中将结果对 1 0 9 + 7 10^9+7 109+7求模。
class Solution {
    public int checkRecord(int n) {
        final int MOD = (int)1e9+7;
        int[][][] dp = new int[n + 1][2][3];   // dp[i][j][k] 表示在第i天,缺席了j次,以及连续迟到了k次的情况下,还可以拿到奖励的排列数量
        dp[0][0][0] = 1;
        for(int i = 1; i <= n; i++){
            // 以P结尾的数量
            for(int j = 0; j <= 1; j++){      // 过去可以是0,1次缺勤
                for(int k = 0; k <= 2; k++){
                    dp[i][j][0] = (dp[i][j][0] + dp[i - 1][j][k]) % MOD;  // 表示今天没有缺勤和迟到,有0,1,2次连续迟到
                }
            }
            // 以A结尾的数量
            for(int k = 0; k <= 2; k++){
                dp[i][1][0] = (dp[i][1][0] + dp[i - 1][0][k]) % MOD; // 表示今天缺勤,过去可以有0,1,2次连续迟到,但是不可以有缺勤
            }
            // 以L结尾的数量
            for(int j = 0; j <= 1; j++){
                for(int k = 1; k <= 2; k++){
                    dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k - 1]) % MOD;  // 今天迟到,过去可以在有0,1次缺勤的情况下,有0,1次连续迟到
                }
            }
        }
        int sum = 0;
        for(int j = 0; j <= 1; j++){
            for(int k = 0; k <= 2; k++){
                sum = (sum + dp[n][j][k]) % MOD;
            }
        }
        
        return sum;
    }
}

由于 d p [ i ] [ ] [ ] dp[i][][] dp[i][][]只会从 d p [ i − 1 ] [ ] [ ] dp[i-1][][] dp[i1][][]获得,因此可以省略dp中的总天数维度,优化空间复杂度。

class Solution {
    public int checkRecord(int n) {
        final int MOD = 1000000007;
        int[][] dp = new int[2][3]; // A 的数量,结尾连续 L 的数量
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++) {
            int[][] dpNew = new int[2][3];
            // 以 P 结尾的数量
            for (int j = 0; j <= 1; j++) {
                for (int k = 0; k <= 2; k++) {
                    dpNew[j][0] = (dpNew[j][0] + dp[j][k]) % MOD;
                }
            }
            // 以 A 结尾的数量
            for (int k = 0; k <= 2; k++) {
                dpNew[1][0] = (dpNew[1][0] + dp[0][k]) % MOD;
            }
            // 以 L 结尾的数量
            for (int j = 0; j <= 1; j++) {
                for (int k = 1; k <= 2; k++) {
                    dpNew[j][k] = (dpNew[j][k] + dp[j][k - 1]) % MOD;
                }
            }
            dp = dpNew;
        }
        int sum = 0;
        for (int j = 0; j <= 1; j++) {
            for (int k = 0; k <= 2; k++) {
                sum = (sum + dp[j][k]) % MOD;
            }
        }
        return sum;
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/student-attendance-record-ii/solution/xue-sheng-chu-qin-ji-lu-ii-by-leetcode-s-kdlm/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值