【LeetCode.552】学生出勤记录 II

题目:学生出勤记录 II

可以用字符串表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:
‘A’:Absent,缺勤
‘L’:Late,迟到
‘P’:Present,到场
如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:

总出勤 计,学生缺勤(’A’)严格 少于两天。
学生 不会 存在 连续 3 天或 连续 3 天以上的迟到(’L’)记录。
给你一个整数 n ,表示出勤记录的长度(次数)。请你返回记录长度为 n 时,可能获得出勤奖励的记录情况 数量 。答案可能很大,所以返回对 109 + 7 取余 的结果。

示例 1:

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

示例 2:

1
2
输入:n = 1
输出:3

示例 3:

1
2
输入:n = 10101
输出:183236316

提示:

1 <= n <= 105


分析:

动态规划做多了,一来就想到动态规划,都忘了还能模拟遍历(不过以我击败5%的结果用暴力法必定超时)

考虑最简单的dp数组dp[i]表示有i天时可获得奖励的天数,0 <= i <= n。但是还需要记录有i天时的缺勤天数和结尾连续迟到天数,为什么是结尾连续迟到天数呢,因为一旦连续迟到天数达到3天,就将dp置0。所以要将dp数组升维再升维变成dp[i][j][k]

dp[i][j][k]表示 “有i天、缺勤j天、结尾连续迟到k天” 的可获得奖励天数

边界:dp[0][0][0] = 1即还没开始上课,这时可以获得出勤奖励

状态转移分为三种情况:到场、迟到、缺勤

到场:今天上课到场,于是迟到天数清零,缺勤保持不变,将前一天不同j和不同k的dp加起来

迟到:今天上课迟到,前一天的迟到天数只能在0和1之间,因为一旦前一天的结尾迟到天数到达2,今天再迟到就没有奖励了,dp置零(置零不用特别计算,新建的未初始化数组就是0,不影响后续计算调用),于是迟到天数加一,缺勤保持不变,将前一天不同j和k - 1的dp加起来

缺勤:今天上课缺勤,前一天的缺勤天数只能是0,理由同上,于是迟到天数清零,缺勤天数加一,将前一天j = 0和不同k的dp加起来


题解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
public:
int checkRecord(int n) {
vector<vector<vector<int>>> dp(n + 1, vector<vector<int>>(2, vector<int>(3)));
dp[0][0][0] = 1;
int mod = pow(10, 9) + 7;
int ans = 0;
for(int i = 1; i <= n; i++){
//P
for(int j = 0; j <= 1; j++){
for(int k = 0; k <= 2; k++){
dp[i][j][0] = (dp[i][j][0] + dp[i - 1][j][k]) % mod;
}
}
//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;
}
}
//A
for(int k = 0; k <= 2; k++){
dp[i][1][0] = (dp[i][1][0] + dp[i - 1][0][k]) % mod;
}
}

for(int j = 0; j <= 1; j++){
for(int k = 0; k <= 2; k++){
ans = (ans + dp[n][j][k]) % mod;
}
}
return ans;
}
};


结语:

动态规划解题思路的一次更新,对于dp数组升维这件事一直都把握不住,这道题目的条件让人容易找到升维切入点,真是受益良多