A clear explanation of Student Attendance Record II using dynamic programming over absence count and late streak.
Problem Restatement
We are given an integer n.
We need to count how many attendance records of length n are eligible for an award.
Each record contains only three characters:
| Character | Meaning |
|---|---|
"A" | Absent |
"L" | Late |
"P" | Present |
A record is valid only if:
- It has strictly fewer than
2absences. - It never has
3or more consecutive late days.
Return the number of valid records modulo 10^9 + 7.
The official constraints are 1 <= n <= 10^5. The examples include n = 2, output 8; n = 1, output 3; and n = 10101, output 183236316.
Input and Output
| Item | Meaning |
|---|---|
| Input | An integer n |
| Output | Number of valid attendance records of length n |
| Modulo | 10^9 + 7 |
| Constraint | 1 <= n <= 100000 |
Example function shape:
def checkRecord(n: int) -> int:
...Examples
For:
n = 1There are three possible records:
"P", "L", "A"All are valid.
So the answer is:
3For:
n = 2There are nine total strings:
PP, PL, PA,
LP, LL, LA,
AP, AL, AAOnly "AA" is invalid because it has two absences.
So the answer is:
8First Thought: Generate All Records
A direct solution is to generate every string of length n using "A", "L", and "P".
There are three choices for every position, so there are:
3 ** npossible records.
For each record, we can check whether it has fewer than two "A" characters and does not contain "LLL".
This works for very small n, but it cannot handle n = 100000.
Problem With Brute Force
The number of records grows exponentially.
| n | Number of records |
|---|---|
| 1 | 3 |
| 2 | 9 |
| 10 | 59049 |
| 20 | 3486784401 |
So we need to count valid records without building them.
The important observation is that we do not need the whole string. We only need enough state to know whether adding the next character keeps the record valid.
Key Insight
At any point, a partial record can be described by two pieces of state:
| State | Meaning |
|---|---|
a | Number of absences used so far: 0 or 1 |
l | Current consecutive late streak: 0, 1, or 2 |
We never need to keep a state with a = 2, because that record is already invalid.
We never need to keep a state with l = 3, because that record is already invalid.
So there are only:
2 * 3 = 6valid states.
For each day, we transition from old states to new states by adding one of three characters:
| Add | Effect |
|---|---|
"P" | Absence count stays the same, late streak becomes 0 |
"A" | Absence count increases by 1, late streak becomes 0 |
"L" | Absence count stays the same, late streak increases by 1 |
Dynamic Programming State
Let:
dp[a][l]mean:
The number of valid records built so far with a absences and a current late streak of length l.
The possible values are:
a in {0, 1}
l in {0, 1, 2}Before building any days, we have one empty record:
dp[0][0] = 1All other states start at 0.
Algorithm
For each day from 1 to n:
- Create a new DP table filled with zero.
- For every state
dp[a][l], try appending"P","A", and"L". - Append
"P":ndp[a][0] += dp[a][l] - Append
"A"only ifa == 0:ndp[1][0] += dp[0][l] - Append
"L"only ifl < 2:ndp[a][l + 1] += dp[a][l] - Replace
dpwithndp.
At the end, sum all six states.
Correctness
The DP state records exactly the information needed to determine whether the next character can be added.
For absences, the only relevant fact is whether the record already has zero or one "A". If it already has one, adding another "A" would make the record invalid.
For late days, the only relevant fact is the current suffix length of consecutive "L" characters. If the suffix length is two, adding another "L" would create "LLL" and make the record invalid.
Each transition appends exactly one valid next character. "P" always keeps the record valid and resets the late streak. "A" is allowed only when no absence has appeared before. "L" is allowed only when the current late streak is less than two.
Every valid attendance record of length n can be produced by these transitions one character at a time. Every record produced by these transitions satisfies both rules. Therefore, summing all DP states after n days gives exactly the number of valid records.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each day updates only six states |
| Space | O(1) | The DP table has fixed size 2 x 3 |
Implementation
class Solution:
def checkRecord(self, n: int) -> int:
MOD = 10**9 + 7
dp = [[0] * 3 for _ in range(2)]
dp[0][0] = 1
for _ in range(n):
ndp = [[0] * 3 for _ in range(2)]
for a in range(2):
for l in range(3):
count = dp[a][l]
if count == 0:
continue
# Add 'P': reset late streak.
ndp[a][0] = (ndp[a][0] + count) % MOD
# Add 'A': allowed only if no absence was used.
if a == 0:
ndp[1][0] = (ndp[1][0] + count) % MOD
# Add 'L': allowed only if it does not create 'LLL'.
if l < 2:
ndp[a][l + 1] = (ndp[a][l + 1] + count) % MOD
dp = ndp
return sum(sum(row) for row in dp) % MODCode Explanation
We start with:
dp[0][0] = 1This represents the empty record. It has zero absences and zero consecutive late days.
For every day, we build a fresh table:
ndp = [[0] * 3 for _ in range(2)]This table stores counts after adding one more character.
Adding "P" is always valid:
ndp[a][0] = (ndp[a][0] + count) % MODIt does not change the absence count, and it resets the late streak.
Adding "A" is allowed only from states with a == 0:
if a == 0:
ndp[1][0] = (ndp[1][0] + count) % MODAfter adding "A", the absence count becomes 1, and the late streak resets.
Adding "L" is allowed only when l < 2:
if l < 2:
ndp[a][l + 1] = (ndp[a][l + 1] + count) % MODThis extends the current late streak by one.
After processing all days, every valid record of length n belongs to exactly one of the six DP states. So we return their sum.
Testing
def run_tests():
s = Solution()
assert s.checkRecord(1) == 3
assert s.checkRecord(2) == 8
assert s.checkRecord(3) == 19
assert s.checkRecord(4) == 43
assert s.checkRecord(10101) == 183236316
print("all tests passed")
run_tests()| Test | Why |
|---|---|
n = 1 | Smallest valid input |
n = 2 | Official sample |
n = 3 | Checks rejection of "AAA" and "LLL" style records |
n = 4 | Checks repeated DP transitions |
n = 10101 | Official large sample with modulo behavior |