Taro Logo

Student Attendance Record II

Hard
Meta logo
Meta
1 view
Topics:
Dynamic Programming

You are given an integer n representing the length of an attendance record. An attendance record consists of the characters 'A' (Absent), 'L' (Late), and 'P' (Present).

A student is eligible for an attendance award if they meet both of the following conditions:

  1. The student has been absent ('A') for strictly fewer than 2 days in total.
  2. The student has never been late ('L') for 3 or more consecutive days.

Your task is to write a function that calculates the number of possible attendance records of length n that make a student eligible for an attendance award. Since the answer can be very large, return it modulo 10^9 + 7.

Example 1:

n = 2
Output: 8
Explanation: The eligible records are "PP", "AP", "PA", "LP", "PL", "AL", "LA", "LL". "AA" is not eligible because it has 2 absences.

Example 2:

n = 1
Output: 3
Explanation: The eligible records are "A", "L", "P".

Constraints:

  • 1 <= n <= 10^5

How would you approach solving this problem efficiently? What are the time and space complexities of your solution?

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What is the maximum value of `n`? Can `n` be zero or negative?
  2. Are leading or trailing 'L's allowed in a valid attendance record?
  3. If the number of valid attendance records exceeds the maximum integer value, how should the result be handled?
  4. Is the input `n` always an integer, or do I need to handle potential non-integer inputs?
  5. Is there any constraint on the number of consecutive 'L's, or is only the total count of 'A's and 'L's important?

Brute Force Solution

Approach

The goal is to find the number of valid attendance records of a given length. A brute force approach involves generating every single possible attendance record and then checking if each record is valid.

Here's how the algorithm would work step-by-step:

  1. First, think of all possible attendance records. Each day can be either Present, Absent, or Late.
  2. Generate every single possible combination of Present, Absent, and Late for the given number of days. For example, if there are only two days, you'd have Present-Present, Present-Absent, Present-Late, Absent-Present, and so on.
  3. For each of these generated records, check if it is valid.
  4. A record is invalid if it contains more than one Absent or more than two consecutive Lates.
  5. If the record contains more than one Absent or has more than two consecutive Lates, discard it because it is not valid.
  6. If the record is valid, keep track of it.
  7. Once you have checked all the possible attendance records, count the number of valid records you kept track of.
  8. That count represents the total number of valid attendance records.

Code Implementation

def student_attendance_record_two_brute_force(number_of_days):
    all_possible_records = []
    
    def generate_records(current_record, days_remaining):
        if days_remaining == 0:
            all_possible_records.append(current_record)
            return
        
        generate_records(current_record + 'P', days_remaining - 1)
        generate_records(current_record + 'A', days_remaining - 1)
        generate_records(current_record + 'L', days_remaining - 1)

    generate_records('', number_of_days)
    
    valid_record_count = 0
    for record in all_possible_records:
        absent_count = 0
        late_streak = 0
        is_valid = True

        for day in record:
            if day == 'A':
                absent_count += 1

            if day == 'L':
                late_streak += 1
            else:
                late_streak = 0

            # Check for invalid condition: more than one absent
            if absent_count > 1:
                is_valid = False
                break

            # Check for invalid condition: more than two consecutive lates
            if late_streak > 2:
                is_valid = False
                break
        
        if is_valid:
            valid_record_count += 1

    return valid_record_count

# Main function for testing the brute force approach.
def main_function(number_of_days):
    # Call the brute force solution.
    result = student_attendance_record_two_brute_force(number_of_days)
    return result

Big(O) Analysis

Time Complexity
O(3^n)The brute force approach generates all possible attendance records of length n, where each day can be one of three states (Present, Absent, or Late). This results in 3^n possible combinations. For each of these combinations, the algorithm checks if it is valid, which involves iterating through the string of length n at most. Therefore, the time complexity is dominated by the generation of all possible combinations, resulting in O(3^n).
Space Complexity
O(3^N)The brute force approach generates every possible attendance record. Each day has three possibilities: Present, Absent, or Late. Therefore, to generate all combinations for N days, we create strings of length N, where each character is one of three possibilities. Thus there will be 3^N possible attendance records stored in memory, resulting in space complexity proportional to 3^N, where N is the number of days. Since each record has length N, one could argue the total space is N * 3^N. However, the problem statement is focused on the number of possible attendance records, so 3^N is more appropriate.

Optimal Solution

Approach

This problem involves counting attendance records with specific constraints on the number of absent and late days. The efficient solution breaks down the problem into smaller, overlapping subproblems, solving each subproblem only once and storing the results to avoid redundant calculations.

Here's how the algorithm would work step-by-step:

  1. Imagine you're building attendance records one day at a time.
  2. Consider each day and the possible states: present, absent, or late.
  3. Instead of trying every single combination, we'll keep track of the number of valid attendance records for each length and possible number of absent/late days.
  4. We'll create a table (or a similar structure) to store the answers for these shorter sequences.
  5. Start with the base cases: how many valid records are there of length 1? (present, absent, or late, so 3).
  6. Then, for each new day, figure out how many new valid records can be created by adding a present, absent, or late day to existing shorter, valid records.
  7. When adding a day, make sure you don't violate the constraints (e.g., more than one absent day or more than two consecutive late days).
  8. The number of valid records for length 'n' will be the sum of all the valid ways to get there from the previous day.
  9. Store the result of how many valid records there are for that length, number of absent days, and consecutive late days, in your table. This prevents you from having to recompute the same answer again.
  10. Continue until you reach the desired length of the attendance record.
  11. The answer will be the number of valid records of length 'n' that satisfy the absent/late day constraints. Add up all the valid attendance records in the table that fulfill the number of absent/late days condition.
  12. Since the numbers might get very large, remember to use the modulo operation to prevent integer overflow.

Code Implementation

def student_attendance_record_ii(record_length):
    modulo = 10**9 + 7

    # Initialize the dp array to store counts.
    dp = [[[0] * 3 for _ in range(2)] for _ in range(record_length + 1)]

    # Base case: Empty record
    dp[0][0][0] = 1

    for i in range(1, record_length + 1):
        # Case 1: Present. Inherit counts from previous day.
        for absent_count in range(2):
            for late_count in range(3):
                dp[i][absent_count][0] = (dp[i][absent_count][0] + dp[i-1][absent_count][late_count]) % modulo

        # Case 2: Absent. Can only have one absent.
        for late_count in range(3):
            if 0 < 2:
                dp[i][1][0] = (dp[i][1][0] + dp[i-1][0][late_count]) % modulo

        # Case 3: Late. Can have at most two consecutive lates.
        for absent_count in range(2):
            for late_count in range(1, 3):
                dp[i][absent_count][late_count] = (dp[i][absent_count][late_count] + dp[i-1][absent_count][late_count-1]) % modulo

    total_attendance_records = 0
    for absent_count in range(2):
        for late_count in range(3):
            total_attendance_records = (total_attendance_records + dp[record_length][absent_count][late_count]) % modulo

    return total_attendance_records

Big(O) Analysis

Time Complexity
O(n)The algorithm iterates through attendance records of length 1 to n. For each day (up to n), it considers the possible states (present, absent, or late) and updates the counts of valid attendance records based on the previous day's records. The computation for each day takes constant time as it only depends on the previously calculated results. Therefore, the time complexity is directly proportional to the length of the attendance record, which is n, resulting in O(n).
Space Complexity
O(N)The solution uses a table (or similar structure) to store the number of valid attendance records for each length up to 'n', along with the possible number of absent days (0 or 1) and consecutive late days (0, 1, or 2). This table has dimensions related to N (length of attendance record). Therefore, the auxiliary space required to store these intermediate results is proportional to N, specifically O(N*1*3) which simplifies to O(N).

Edge Cases

CaseHow to Handle
n = 0Return 1 as there is one way to have a record with length 0.
n = 1Return 3 as there are three possibilities: A, L, P.
n = 2Return 8, covering all possibilities: PP, PA, AP, AA, PL, LP, LA, AL.
Maximum input size (n is large, e.g., 100000)Use dynamic programming with memoization to avoid redundant calculations and ensure the solution scales efficiently; also, make sure to use modulo operation at each step to prevent integer overflow.
Integer overflow when calculating combinationsUse modulo operator % (10^9 + 7) after each calculation to keep the result within the integer range.
Input where only 'A' is allowedThe answer will be zero if n > 1 since more than one absent is not allowed.
Input where no 'A' is allowedCalculations change but still follow same dynamic programming or recursive methodology, summing possibilities of only L and P.
When n is very large, the number of combinations becomes extremely large causing performance problems if not using dynamic programming.Use dynamic programming for efficient computation, avoiding redundant calculations, for time complexity O(n).