Problem
An attendance record for a student can be represented as a string where each character signifies whether the student was absent, late, or present on that day. The record only contains the following three characters:
'A'
: Absent.'L'
: Late.'P'
: Present.
Any student is eligible for an attendance award if they meet both of the following criteria:
- The student was absent (
'A'
) for strictly fewer than 2 days total. - The student was never late (
'L'
) for 3 or more consecutive days.
Given an integer n
, return the number of possible attendance records of length n
that make a student eligible for an attendance award. The answer may be very large, so return it modulo 10^9 + 7
.
Examples
Example 1:
Input: n = 2
Output: 8
Explanation: There are 8 records with length 2 that are eligible for an award:
"PP", "AP", "PA", "LP", "PL", "AL", "LA", "LL"
Only "AA" is not eligible because there are 2 absences (there need to be fewer than 2).
Example 2:
Input: n = 1
Output: 3
Example 3:
Input: n = 10101
Output: 183236316
Solution
Method 1 - Recursion
Code
Java
class Solution {
public int checkRecord(int n) {
return dfs(n, 0, 0, 0);
}
private static final int MOD = (int) 1e9 + 7;
private int dfs(int n, int i, int a, int l) {
if (i == n) {
return 1;
}
// we set l to 0, as late has to be consecutive
// as soon as we set A or P, we set L to 0
int withP = dfs(n, i + 1, a, 0);
int withA = a < 1 ? dfs(n, i + 1, a + 1, 0): 0; // absence can be max 1
int withConsL = l < 2 ? dfs(n, i + 1, a, l + 1): 0; // consecutive late should be less than 3
return ((withP + withA) % MOD + withConsL) % MOD;
}
}
Complexity
- ⏰ Time complexity:
O(2^n)
(not3^n
because only 1 A is allowed in the string). - 🧺 Space complexity:
O(n)
using recursion stack.
Method 2 - Top Down DP with memoization
Why DP?
We can use DP in this problem, the reason is:
Optimal Substructure
A problem has optimal substructure if the solution to the problem can be composed of optimal solutions to its subproblems. In the case of this student attendance record problem, we want to find the total number of valid attendance combinations for n
days. Each day we make a decision: the student can be present (P), absent (A), or late (L), which will influence the remaining combinations. The solution to n
days is built upon the solutions to n-1
days, which constitutes the optimal substructure property.
Overlapping Subproblem
A problem has overlapping subproblems when recursive problem-solving would visit the same subproblems multiple times. In previous code, dfs
is called recursively with different combinations of days (i
), absences (a
), and late days (l
). For the absent and late counts, there is a small range of valid inputs; specifically, a
can only be 0 or 1, and l
can only be 0, 1, or 2 before reaching a base case. If you consider various levels of the recursion and different paths through the recursive calls, you’d visualize a tree of recursive calls where many branches will call dfs
with the same (n, i, a, l)
parameters again and again.
dfs(0,0,0) Day 1
+-- dfs(1,0,0) Day 2 (P)
| +-- dfs(2,0,0) Day 3 (PP) (Can add 'P', 'A', or 'L' on the next day)
| | +-- ...
| +-- dfs(2,1,0) Day 3 (PA) (Can add 'P' or 'L', 'A' is maxed out)
| | +-- ... (Overlaps with dfs(2,0,0) when adding 'P')
| +-- dfs(2,0,1) Day 3 (PL) (Can add 'P', 'A', one more 'L' is allowed)
| +-- ... (Overlaps with dfs(2,0,0) and dfs(2,1,0) when adding 'P' or 'A')
+-- dfs(1,1,0) Day 2 (A)
| +-- dfs(2,1,0) Day 3 (AP)
| | +-- ... (Already seen with dfs(2,1,0) under dfs(1,0,0))
| +-- dfs(2,1,1) Day 3 (AL)
| +-- ... (Unique path since we have an 'A' before)
+-- dfs(1,0,1) Day 2 (L)
+-- dfs(2,0,1) Day 3 (LP)
| +-- ...
+-- dfs(2,1,0) Day 3 (LA)
| +-- ... (Overlaps with dfs(2,1,0) after dfs(1,0,0) with 'PA')
+-- dfs(2,0,2) Day 3 (LL)
+-- ... (Can add only 'P' or 'A' next, no 'L' allowed due to rule)
Notice how certain calls to dfs
occur multiple times with the same parameters, for example, dfs(2,1,0)
:
- Once as a result of two ‘P’s followed by an ‘A’,
- Once as a result of a ‘P’ followed by an ‘A’ and then another ‘P’.
Code
Java
class Solution {
public int checkRecord(int n) {
return dfs(n, 0, 0, 0, new int[n][2][3]);
}
private static final int MOD = (int) 1e9 + 7;
private int dfs(int n, int i, int a, int l, int[][][] cache) {
if (i == n) {
return 1;
}
if(cache[i][a][l] != 0) {
return cache[i][a][l];
}
// we set l to 0, as L has to be consecutive
// as soon as we set A or P, we set L to 0
int withP = dfs(n, i + 1, a, 0, cache);
int withA = a < 1 ? dfs(n, i + 1, a + 1, 0, cache): 0; // absence can be max 1
int withConsL = l < 2 ? dfs(n, i + 1, a, l + 1, cache): 0; // consecutive late should be less than 3
int total = ((withP + withA) % MOD + withConsL) % MOD;
return cache[i][a][l] = total;
}
}
Complexity
- ⏰ Time complexity:
O(n)
- 🧺 Space complexity:
O(n)
Method 3 - Bottom-up DP
Algorithm
Let dp[i][a][l]
denote the number of valid sequences of length i
where:
- There can be at most
a
A’s in the entire sequence. - There can be at most
l
trailing L’s.
We will fill dp
till dp[n][1][2]
.
Base Case
On day 0, since there’s no prior record, all possibilities are valid (1
for each combination of a
and l
).
Recurrence relation
val = f[i - 1][a][2]
: This represents the number of valid records ending on dayi-1
with at most two consecutive ‘L’s (denoted byl = 2
). This value is used as the starting point for building the current day’s possibilities. So, we just addP
to the record, and hence we are just carrying forward this value for currentdp[i][a][l]
.val += f[i - 1][a - 1][2] if a > 0
: This condition adds the number of valid records ending on dayi-1
with one ‘A’ (denoted bya = 1
) and at most two consecutive ‘L’s (k = 2
). Since we can’t have more than one ‘A’, this is only added ifa
is greater than 0.val += f[i - 1][j][l - 1])
: This condition adds the number of valid records ending on dayi-1
with the same number of ‘A’s (a
) but one less consecutive ‘L’ (k - 1
). This allows continuing a sequence of ‘L’s as long as it doesn’t exceed two.
Code
Java
class Solution {
private static final int MOD = (int) 1e9 + 7;
public int checkRecord(int n) {
int[][][] dp = new int[n + 1][2][3];
dp[0] = new int[][]{{1, 1, 1}, {1, 1, 1}};
for (int i = 1; i <= n; i++) {
for (int a = 0; a < 2; a++) {
for (int l = 0; l < 3; l++) {
int val = dp[i - 1][a][2]; // ..P
if (a > 0) {
val = (val + dp[i - 1][a - 1][2]) % MOD; // ..A
}
if (l > 0) {
val = (val + dp[i - 1][a][l - 1]) % MOD; // ..L
}
dp[i][a][l] = val;
}
}
}
return dp[n][1][2];
}
}
Complexity
- ⏰ Time complexity:
O(n)
- 🧺 Space complexity:
O(n)
because2*3*(n+1) = 6n ~ O(n)
Method 4 - State Optimized DP
We can actually solve this problem with O(1) space DP. Previous approach uses a 3D array dp
, but we can just keep the number of valid records for a previous day, and calculate today’s value based on that.
We can use prevDP[a][l]
to count valid student attendance records with a
Absences and l
consecutive late arrivals. Now a
at max 2, and l
is at max 3.
For a
:
prevDP[0]
: This represents the number of valid records ending on previous day with no ‘A’s (i.e.,a = 0
).prevDP[1]
: This represents the number of valid records ending on previous day with one ‘A’ (i.e.,a = 1
).
For l
, we will similar values.
For day 0, we know all the values will be 1.
Code
Java
class Solution {
private static final int MOD = (int) 1e9 + 7;
public int checkRecord(int n) {
int prevDP[][] = new int[2][3];
prevDP[0] = new int[][]{{1, 1, 1}, {1, 1, 1}};
for(int i = 1; i <= n; i++){
int newDP[][] = new int[2][3];
for(int a = 0; a < 2; a++){
for(int l = 0; l < 3; l++){
//Pick P
newDP[a][l] += prevDP[a][2];
if(a > 0){
//Pick A
newDP[a][l] += prevDP[a - 1][2];
newDP[a][l] %= MOD;
}
if(l > 0){
// Pick L
newDP[a][l] += prevDP[a][l - 1];
newDP[a][l] %= MOD;
}
}
}
prevDP = newDP;
}
return prevDP[1][2];
}
}
Complexity
- ⏰ Time complexity:
O(n)
- 🧺 Space complexity:
O(1)
(O(2*3)
for matrix, henceO(1)
)
Method 5 - Matrix Exponentiation
From previous solution, we can see we get only 6 states, and next state is always generated from the current state.
#todo complete this step