A student attendance record is a string that only contains the following three characters:
- 'A' : Absent.
- 'L' : Late.
- 'P' : Present.
A record is regarded as rewardable if it doesn't contain more than one 'A' (absent) or more than two continuous 'L' (late).
给定一个正整数n,返回长度为n的序列的个数,这个序列是所有可能出席的记录,也就是被认为可以奖赏的。答案可能很大,返回值模上109 + 7。学生出席记录的字符串由‘A’,'L','P'三种字符构成,分别代表出席,迟到和缺席。一条被认为是可奖赏的记录不能超过一次缺席(A)和不能超过连续两个迟到(L)
思路:注意到题目中告诉我们答案可能很大,会比109 + 7还大,所以不能采用穷举法或者递归的方法。鉴于前几周老师一直都在讲动态规划的方法,自己也做了一些练习,所以很快就能想到这道题要用动态规划的方法来解。首先看题目的要求,一条记录当中最多能出现一个A和连续两个L,所以答案跟这两个变量都相关,另外,答案是根据输入的n来输出可能性,所以也与n有关。因此,要用一个三维数组dp[n][i][j]来记录答案,表示长度为n,包含i个A和以连续j个L结尾的字符串所有可能的个数。显然i的取值是0和1,j的取值是0,1,2。因此可以这样初始化dp[1]:dp[1][0][0] = 1; dp[1][0][1] = 1; dp[1][0][2] = 0; dp[1][1][0] = 1; dp[1][1][1] = 0; dp[1][1][2] = 0。 因为字符串长度仅为1,所以出现两个L,一个L和一个A的情况不会出现。接下来讨论状态转移。
状态转移有很多种可能,如果长度为i时,没有出现一个A并且结尾不是L的情况,可以推出长尾为i-1时,也没有出现一个A和结尾不是L,dp[i][0][0] = sum(dp[i-1][0]); 没有出现一个A并且以一个L结尾的情况,可以推出长尾为i-1时,也没有出现一个A和结尾不是L,dp[i][0][1] = dp[i-1][0][0]; 没有出现一个A并且以两个L结尾的情况,可以推出长尾为i-1时,也没有出现一个A和结尾是一个L,dp[i][0][2] = dp[i-1][0][1]; 出现了一个A和结尾不是L的情况,可以推出长尾为i-1时,可能没有出现一个A或者出现过一个A并且结尾不是L,dp[i][1][0] = sum(dp[i-1][0]) + sum(dp[i-1][1]); 剩余两种情况与上面类似。
class Solution {
public:
int sum(int nums[]) {
long ans = 0;
for (int i = 0; i < 3; i ++)
ans += nums[i];
return ans % 1000000007;
}
int checkRecord(int n) {
int mod = 1000000007;
int dp[100001][2][3];
dp[1][0][0] = 1;
dp[1][0][1] = 1;
dp[1][0][2] = 0;
dp[1][1][0] = 1;
dp[1][1][1] = 0;
dp[1][1][2] = 0;
for(int i = 2; i <= n; ++i)
{
dp[i][0][0] = sum(dp[i-1][0]);
dp[i][0][1] = dp[i-1][0][0];
dp[i][0][2] = dp[i-1][0][1];
dp[i][1][0] = (sum(dp[i-1][0]) + sum(dp[i-1][1])) % mod;
dp[i][1][1] = dp[i-1][1][0];
dp[i][1][2] = dp[i-1][1][1];
}
return (sum(dp[n][0]) + sum(dp[n][1])) % mod;
}
};
虽然这样写通过了,但是感觉不够简洁,因此看了大神们的解法,可以这样子来做。dp[i][j][k]表示长度为i的时候,出现A的个数为j,出现连续k个L。
dp[0]全部初始化为1,转移方程是这样的
int val = dp[i-1][j][2];
if (j > 0) val = (val + dp[i-1][j-1][2]) % MOD;
if (k > 0) val = (val + dp[i-1][j][k-1]) % MOD;
dp[i][j][k] = val;