The "Student Attendance Record II" problem asks us to determine how many possible attendance records of length n
can be constructed, given certain rules. Each record is a string where each character represents a student's attendance on a day, and can be:
'A'
: Absent'L'
: Late'P'
: Present'A'
(absent) in total.'L'
(late).n
, return the total number of rewardable records of length n
modulo 10^9 + 7
.
At first glance, it might seem reasonable to try generating all possible strings of length n
and count those that meet the constraints. However, this brute-force approach would be extremely inefficient because the number of possible strings grows exponentially with n
(specifically, 3^n
).
To make the problem tractable, we need to use a more clever approach. The two main constraints ('A'
at most once, 'L'
not more than twice in a row) suggest that dynamic programming (DP) is a good fit. DP allows us to build up solutions for shorter records and use those to construct longer ones, while keeping track of the necessary state (such as how many 'A'
s we've used and how many consecutive 'L'
s are at the end).
The key insight is to keep track of:
'A'
s used so far (0 or 1)'L'
s (0, 1, or 2)We will use a dynamic programming (DP) approach. The main idea is to define a DP state that captures:
i
: the current length of the recorda
: the number of 'A'
s used so far (0 or 1)l
: the number of consecutive 'L'
s at the end (0, 1, or 2)dp[i][a][l]
represent the number of valid records of length i
with a
'A's and l
trailing 'L's.
dp[0][0][0] = 1
.
dp[i][a][l]
, we can add one more character:
dp[i+1][a][0] += dp[i][a][l]
a == 0
; this resets trailing 'L's to 0 and increases the 'A' count by 1.dp[i+1][1][0] += dp[i][0][l]
l < 2
; this increases the trailing 'L's by 1, 'A' count unchanged.dp[i+1][a][l+1] += dp[i][a][l]
10^9 + 7
.
dp[n][a][l]
for a
in [0,1] and l
in [0,1,2].
This method ensures that we never construct an invalid record, and efficiently builds up the solution for larger n
using results from smaller ones.
Let's walk through a small example with n = 2
:
n=2
.
For larger n
, the DP efficiently computes the answer without generating all records.
Brute-force approach:
O(3^n)
(since each position can be 'A', 'L', or 'P')O(n)
for recursion stack or storing each recordn
.
Dynamic Programming approach:
O(n)
(since there are n
steps, and for each, we update at most 2 (A count) * 3 (L count) = 6 states)O(n)
(we can even optimize to O(1)
by only keeping the previous step's DP states)n
(up to 105 or more).
To solve the "Student Attendance Record II" problem, we use dynamic programming to efficiently count the number of valid attendance records. By keeping track of the number of 'A's used and the number of consecutive 'L's at the end, we avoid generating all possible strings and instead build up the answer iteratively. This approach is both elegant and efficient, leveraging the structure of the problem to achieve a solution that works for large input sizes.
MOD = 10 ** 9 + 7
class Solution:
def checkRecord(self, n: int) -> int:
# dp[a][l]: number of ways with a 'A's and l trailing 'L's
dp = [[0] * 3 for _ in range(2)]
dp[0][0] = 1
for _ in range(n):
new_dp = [[0] * 3 for _ in range(2)]
for a in range(2):
for l in range(3):
# Add 'P'
new_dp[a][0] = (new_dp[a][0] + dp[a][l]) % MOD
# Add 'A'
if a == 0:
new_dp[1][0] = (new_dp[1][0] + dp[a][l]) % MOD
# Add 'L'
if l < 2:
new_dp[a][l+1] = (new_dp[a][l+1] + dp[a][l]) % MOD
dp = new_dp
return sum(dp[a][l] for a in range(2) for l in range(3)) % MOD
class Solution {
public:
int checkRecord(int n) {
const int MOD = 1e9 + 7;
int dp[2][3] = {0};
dp[0][0] = 1;
for (int i = 0; i < n; ++i) {
int new_dp[2][3] = {0};
for (int a = 0; a < 2; ++a) {
for (int l = 0; l < 3; ++l) {
// Add 'P'
new_dp[a][0] = (new_dp[a][0] + dp[a][l]) % MOD;
// Add 'A'
if (a == 0)
new_dp[1][0] = (new_dp[1][0] + dp[a][l]) % MOD;
// Add 'L'
if (l < 2)
new_dp[a][l+1] = (new_dp[a][l+1] + dp[a][l]) % MOD;
}
}
memcpy(dp, new_dp, sizeof(dp));
}
int res = 0;
for (int a = 0; a < 2; ++a)
for (int l = 0; l < 3; ++l)
res = (res + dp[a][l]) % MOD;
return res;
}
};
class Solution {
public int checkRecord(int n) {
int MOD = 1000000007;
int[][] dp = new int[2][3];
dp[0][0] = 1;
for (int i = 0; i < n; i++) {
int[][] newDp = new int[2][3];
for (int a = 0; a < 2; a++) {
for (int l = 0; l < 3; l++) {
// Add 'P'
newDp[a][0] = (newDp[a][0] + dp[a][l]) % MOD;
// Add 'A'
if (a == 0) {
newDp[1][0] = (newDp[1][0] + dp[a][l]) % MOD;
}
// Add 'L'
if (l < 2) {
newDp[a][l+1] = (newDp[a][l+1] + dp[a][l]) % MOD;
}
}
}
dp = newDp;
}
int res = 0;
for (int a = 0; a < 2; a++) {
for (int l = 0; l < 3; l++) {
res = (res + dp[a][l]) % MOD;
}
}
return res;
}
}
var checkRecord = function(n) {
const MOD = 1e9 + 7;
let dp = Array.from({length: 2}, () => Array(3).fill(0));
dp[0][0] = 1;
for (let i = 0; i < n; ++i) {
let newDp = Array.from({length: 2}, () => Array(3).fill(0));
for (let a = 0; a < 2; ++a) {
for (let l = 0; l < 3; ++l) {
// Add 'P'
newDp[a][0] = (newDp[a][0] + dp[a][l]) % MOD;
// Add 'A'
if (a === 0)
newDp[1][0] = (newDp[1][0] + dp[a][l]) % MOD;
// Add 'L'
if (l < 2)
newDp[a][l+1] = (newDp[a][l+1] + dp[a][l]) % MOD;
}
}
dp = newDp;
}
let res = 0;
for (let a = 0; a < 2; ++a)
for (let l = 0; l < 3; ++l)
res = (res + dp[a][l]) % MOD;
return res;
};