# 552. Student Attendance Record II

**<font color=red>难度: Hard</font>**

## 刷题内容

> 原题连接

* https://leetcode.com/problems/student-attendance-record-ii/

> 内容描述

```
Given a positive integer n, return the number of all possible attendance records with length n, which will be regarded as rewardable. The answer may be very large, return it after mod 109 + 7.

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).

Example 1:
Input: n = 2
Output: 8 
Explanation:
There are 8 records with length 2 will be regarded as rewardable:
"PP" , "AP", "PA", "LP", "PL", "AL", "LA", "LL"
Only "AA" won't be regarded as rewardable owing to more than one absent times. 
Note: The value of n won't exceed 100,000.
```

## 解题方案

> 思路 1
******- 时间复杂度: O(3^N)******- 空间复杂度: O(3^N)******

dfs，超时

```
class Solution:
    def checkRecord(self, n):
        """
        :type n: int
        :rtype: int
        """
        self.res = 0
        def dfs(cur, cnt_a):
            if len(cur) == n:
                self.res += 1
                return 
            if cnt_a < 1:
                dfs(cur+'A', cnt_a+1)
            if len(cur) < 2 or not (cur[-1] == cur[-2] == 'L'):
                dfs(cur+'L', cnt_a)
            dfs(cur+'P', cnt_a)
        dfs('', 0)
        return self.res
```


> 思路 2
******- 时间复杂度: O(N)******- 空间复杂度: O(N)******

自己的精华总结, 发的[第二篇post](https://leetcode.com/problems/student-attendance-record-ii/discuss/220364/Python-O(N)-time-and-O(N)-space-detailed-explanation)

```
- dp[i+1] means the number of all possible attendance (without 'A') records with length i 
- then we can get dp[i] = dp[i-1] + dp[i-2] + dp[i-3], we can think of this way, dp[i] can end with 'P' or 'L', then
dp[i] = dp[i].endwith('P') + dp[i].endwith('L')
      = dp[i].endwith('P') + dp[i].endwith('PL') + dp[i].endwith('LL')
      = dp[i].endwith('P') + dp[i].endwith('PL') + dp[i].endwith('PLL') + dp[i].endwith('LLL')

but we know that dp[i].endwith('LLL') is not allowed, 
so dp[i] = dp[i].endwith('P') + dp[i].endwith('PL') + dp[i].endwith('PLL')
         = dp[i-1] + dp[i-2] + dp[i-3]
     
because we can simply regard dp[i].endwith('P') as dp[i-1] + 'P', so the number of all possible attendance equals to dp[i-1],
similarly, dp[i].endwith('PL') = dp[i-2] and dp[i].endwith('PLL') = dp[i-3]


then the number of all possible attendance (with 'A') records with length n is:
sum(dp[i+1] * dp[n-i] i = 0,1,...,n)

why sum(dp[i+1] * dp[n-i] i = 0,1,...,n)? 
- because we can now insert one 'A' at any point , so the 'A' can be at index of 0,1,2,...,n-1
- or we don't have an 'A', which means 'A' is at the index of n, which is not included in the final string
     
For n = 3, there are totally 4 ways to represent all possible attendance:
1. dp[1] 'A' dp[3]
2. dp[2] 'A' dp[2]
2. dp[3] 'A' dp[1]
2. dp[4] without 'A'


```


beats 46.15%

```python
class Solution:
    def checkRecord(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n == 1:
            return 3
        dp = [1, 1, 2]
        M = 10 ** 9 + 7
        for i in range(2, n+1):
            dp.append((dp[i] + dp[i-1] + dp[i-2]) % M)
        res = 0
        for i in range(n+1):
            res += (dp[i+1] * dp[(n+1) - 1 - i]) % M # i 代表 'A' 前面的长度，(n+1) - 1 - i 代表 'A' 后面的长度
            res %= M
        return res
```

> 思路 3
******- 时间复杂度: O(N)******- 空间复杂度: O(N)******

参考[solution](https://leetcode.com/problems/student-attendance-record-ii/solution/)

```
dp[i] means the number of all possible attendance (without 'A') records with length i 

所以有一种新的状态转移方程，更简单一些
dp[i] = dp[i-1].endwith('P') + dp[i-1].endwith('L')
      = dp[i-1].endwith('P') + dp[i-1].endwith('PL') + dp[i-1].endwith('LL')
      = dp[i-1].endwith('P') + dp[i-1].endwith('PL') + dp[i-1].endwith('PLL') + dp[i-1].endwith('LLL')
      
还是跟思路2一样，
but we know that dp[i].endwith('LLL') is not allowed, 
so dp[i] = dp[i].endwith('P') + dp[i].endwith('PL') + dp[i].endwith('PLL')
         = 2 * dp[i-1] - dp[i-4]
         
其他都跟思路2一样

```

```python
class Solution:
    def checkRecord(self, n):
        """
        :type n: int
        :rtype: int
        """
        dp = [1,2,4,7]
        M = 10 ** 9 + 7
        
        def cal(n):
            if n <= 3:
                return dp[n]
            return (2 * dp[n-1] - dp[n-4]) % M
        
        for i in range(4, n+1):
            dp.append(cal(i))
        res = 0
        for i in range(n+1):
            if i == n: # 因为当 i == n 时，n-1-i 小于0了，所以要单独考虑
                res += dp[n] 
            else:
                res += (dp[i] * dp[n-1-i]) % M # i 代表 'A' 前面的长度，n-1-i 代表 'A' 后面的长度
            res %= M
        return res
```

> 思路 3
******- 时间复杂度: O(N)******- 空间复杂度: O(1)******

参考[solution](https://leetcode.com/problems/student-attendance-record-ii/solution/)

state machine, axly represents the number of strings of length i containing x a's and ending with y l's

beats 84.62%

```python
class Solution:
    def checkRecord(self, n):
        """
        :type n: int
        :rtype: int
        """
        M = 10 ** 9 + 7
        a0l0 = 1
        a0l1 = a0l2 = a1l0 = a1l1 = a1l2 = 0
        
        for i in range(n):
            new_a0l0 = (a0l0 + a0l1 + a0l2) % M
            new_a0l1 = a0l0
            new_a0l2 = a0l1
            new_a1l0 = (a0l0 + a0l1 + a0l2 + a1l0 + a1l1 + a1l2) % M
            new_a1l1 = a1l0
            new_a1l2 = a1l1
            a0l0 = new_a0l0
            a0l1 = new_a0l1
            a0l2 = new_a0l2
            a1l0 = new_a1l0
            a1l1 = new_a1l1
            a1l2 = new_a1l2

        return (a0l0 + a0l1 + a0l2 + a1l0 + a1l1 + a1l2) % M
```


或者可以写的更简洁一些，用另外一种观察state machine的方式

beats 100%

```python
class Solution:
    def checkRecord(self, n):
        """
        :type n: int
        :rtype: int
        """
        M = 10 ** 9 + 7
        a0l0 = 1
        a0l1 = a0l2 = a1l0 = a1l1 = a1l2 = 0
        
        for i in range(n+1):
            new_a0l0 = (a0l0 + a0l1 + a0l2) % M
            a0l2 = a0l1
            a0l1 = a0l0
            a0l0 = new_a0l0
            new_a1l0 = (a0l0 + a1l0 + a1l1 + a1l2) % M
            a1l2 = a1l1
            a1l1 = a1l0
            a1l0 = new_a1l0
        # 因为多循环了一圈，此时的 a0l0 相当于上一圈的 (a0l0 + a0l1 + a0l2 + a1l0 + a1l1 + a1l2)
        return a1l0
```





















