学生出勤记录II
给定一个正整数 n,返回长度为 n 的所有可被视为可奖励的出勤记录的数量。 答案可能非常大,你只需返回结果mod 109 + 7的值。
学生出勤记录是只包含以下三个字符的字符串:
‘A’ : Absent,缺勤
‘L’ : Late,迟到
‘P’ : Present,到场
如果记录不包含多于一个’A’(缺勤)或超过两个连续的’L’(迟到),则该记录被视为可奖励的。
思路(递归->动态规划)
首先,假设字符串只包含字符
L
L
L 和
P
P
P。
A
A
A 产生的影响放在后面考虑。
假设
f
[
n
]
f[n]
f[n] 表示长度为
n
n
n 的可奖励字符串的数目(只包含字母
L
L
L 和
P
P
P)。下图说明计算
f
[
n
]
f[n]
f[n] 的过程:
上图解释了如何将长度为
n
n
n 的可奖励字符串拆成结尾是
L
L
L或者
P
P
P 的长度为
n
−
1
n-1
n−1 的 2 个字符串。如果长度为
n
−
1
n-1
n−1 的字符串是可奖励的,那么将
P
P
P 接在这个字符串后面,得到长度为
n
n
n 的字符串,一定也是可奖励的。因此,每一个长度为
n
−
1
n-1
n−1 的字符串后面接上
P
P
P 都可以对应一个长度为
n
n
n 的字符串,这使得
f
[
n
−
1
]
f[n-1]
f[n−1] 对
f
[
n
]
f[n]
f[n] 可以产生贡献。
对于以 L L L 结尾的字符串,是否可奖励取决于对应的长度为 n − 3 n-3 n−3 的字符串。所以我们需要考虑所有长度为 n − 3 n-3 n−3 的可奖励字符串。在 4 种可能的组合中,第四种也就是以 L L LL LL 开始的会导致一个不可奖励字符串。由于我们在考虑长度为 n − 3 n-3 n−3 的可奖励字符串,为了避免在 n − 1 n-1 n−1 的时候已经是不可奖励的,第四种情况的不可奖励数目,可认为是长度为 n − 4 n-4 n−4 的字符串必须以 P P P 结尾对应的奖励数目。
所以除了在长度为 n − 4 n-4 n−4 的可奖励字符串后面的 P L L PLL PLL ,其他所有长度为 n − 1 n-1 n−1 的可奖励字符串都可以对长度为 n n n 的可奖励字符串做出贡献。所以 f [ n − 1 ] − f [ n − 4 ] f[n-1] - f[n-4] f[n−1]−f[n−4] 会对 f [ n ] f[n] f[n] 产生贡献。
所以,递归表达式为:
f
[
n
]
=
2
f
[
n
−
1
]
−
f
[
n
−
4
]
f[n] = 2f[n-1] - f[n-4]
f[n]=2f[n−1]−f[n−4]
现在,我们需要考虑加入字符
A
A
A 造成的影响。我们知道最多可以将 1 个
A
A
A 放入一个可奖励字符串中,所以我们考虑如下两种情况:
1、没有
A
A
A:可奖励字符串数目就是
f
[
n
]
f[n]
f[n];
2、有一个
A
A
A:这个
A
A
A 可能出现在
n
n
n 个位置中的任何一个位置。如果
A
A
A 出现在字符串的位置
i
i
i, 可奖励字符串的总数为:
∑
i
=
1
n
(
f
[
i
−
1
]
∗
f
[
n
−
i
]
)
\sum_{i=1}^{n} (f[i-1] * f[n-i])
∑i=1n(f[i−1]∗f[n−i]).
代码(动态规划)
我们每次计算 f [ i ] f[i] f[i] 都需要使用递归函数,直到走到字符串最开始的位置。如果我们使用记录下来的 f [ j ] f[j] f[j] 去更新 f [ i ] f[i] f[i],我们可以节省非常大量的计算时间.
class Solution:
def checkRecord(self, n: int) -> int:
f = [1,2,4,7]
for i in range(4,n+1):
f.append(((2*f[i-1])%1000000007+ (1000000007-f[i-4]))%1000000007)
sum = f[n]
for i in range(1,n+1):
sum += (f[i-1] * f[n-i])%1000000007
return sum%1000000007