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:

  1. 'A' : Absent.
  2. 'L' : Late.
  3. '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.

这道题是之前那道 Student Attendance Record I 的拓展,但是比那道题难度要大的多。从题目中说结果要对一个很大的数取余,说明结果是一个很大很大的数。一般来说这种情况不能用递归来求解,可能会爆栈,所以要考虑利用数学方法或者动态规划 Dynamic Programming 来做。其实博主最先看到这题的时候,心想这不就是高中时候学的排列组合的题吗,于是又在想怎么写那些A几几,C几几的式子来求结果,可惜并没有做出来。现在想想怎么当初高中的自己这么生猛,感觉啥都会的样子,上知天文下知地理,数理化生样样精通的感觉,燃鹅随着时间的推移,所有的一切都还给了老师。总感觉这题用数学的方法应该也可以解,但是看网上的大神们都是用 DP 做的,没办法,那只能用 DP 来做了。下面这种做法来自 大神 lixx2100 的帖子,这里定义一个三维的 dp 数组,其中 dp[i][j][k] 表示数组前i个数字中,最多有j个A,最多有k个连续L的组合方式,那么最终要求的结果就保存在dp[n][1][2]中。然后来考虑如何求 dp[i][j][k] 的状态转移方程,首先来取出前一个状态下的值,就是前 i-1 个数的值 dp[i-1][j][2],即数组前 i-1 个数中,最多有j个A,最多有2个连续L的排列方式,然后如果 j>0,那么再加上 dp[i-1][j-1][2],即加上了最多有j-1个A的情况,并对超大数取余;如果 k>0,则再加上 dp[i-1][j][k-1],即加上了最多有j个A,最多有 k-1 个连续L的排列方式,其实博主并没有完全理解为什么要这么更新,如果有大神们理解了这么做的含义,请不吝赐教,在下方留言告知博主啊~

解法一:

class Solution {
public:
int checkRecord(int n) {
int M = 1e9 + ;
int dp[n + ][][] = {};
for (int j = ; j < ; ++j) {
for (int k = ; k < ; ++k) {
dp[][j][k] = ;
}
}
for (int i = ; i <= n; ++i) {
for (int j = ; j < ; ++j) {
for (int k = ; k < ; ++k) {
int val = dp[i - ][j][];
if (j > ) val = (val + dp[i - ][j - ][]) % M;
if (k > ) val = (val + dp[i - ][j][k - ]) % M;
dp[i][j][k] = val;
}
}
}
return dp[n][][];
}
};

下面这种方法来自 大神 KJer 的帖子,大神帖子里面的讲解写的很详细,很赞,也不难读懂。定义了三个 DP 数组 P, L, A,其中 P[i] 表示数组 [0,i] 范围内以P结尾的所有排列方式,L[i] 表示数组 [0,i] 范围内以L结尾的所有排列方式,A[i] 表示数组 [0,i] 范围内以A结尾的所有排列方式。那么最终所求的就是 P[n-1] + L[n-1] + A[n-1] 了,难点就是分别求出 P, L, A 数组的递推公式了。

首先来看P数组的,P字符没有任何限制条件,可以跟在任何一个字符后面,所以有 P[i] = A[i-1] + P[i-1] + L[i-1]

再来看L数组的,L字符唯一的限制条件是不能有超过两个连续的L,那么在P和L字符后面可以加1一个L,如果前一个字符是L,要看再前面的一位是什么字符,如果是P或着A的话,可以加L,如果是L的话,就不能再加了,否则就连续3个了,所以有 L[i] = A[i-1] + P[i-1] + A[i-2] + P[i-2]

最后来看A数组的,这个比较麻烦,字符A的限制条件是整个字符串最多只能有1个A,那么当前一个字符是A的话,就不能再加A来,当前一个字符是P或者L的话,要确定之前从没有A出现过,才能加上A。那么实际上还需要定义两个数组 P1, L1, 其中 P1[i] 表示数组 [0,i] 范围内以P结尾的不包含A的所有排列方式,L1[i] 表示数组 [0,i] 范围内以L结尾的不包含A的所有排列方式,根据前两种情况不难推出 P1 和 L1 的递推公式,再加上A的递推公式如下:

A[i] = P1[i-1] + L1[i-1]

P1[i] = P1[i-1] + L1[i-1]

L1[i] = P1[i-1] + P1[i-2]

将第二第三个等式多次带入第一个等式,就可以将 P1 和 L1 消掉,可以化简为:

A[i] = A[i-1] + A[i-2] + A[i-3]

这样就可以少定义两个数组了,状态转移方程有了,代码也就不难写了:

解法二:

class Solution {
public:
int checkRecord(int n) {
int M = 1e9 + ;
vector<int> P(n), L(n), A(n);
P[] = ; L[] = ; A[] = ;
if (n > ) { L[] = ; A[] = ; }
if (n > ) A[] = ;
for (int i = ; i < n; ++i) {
P[i] = ((P[i - ] + L[i - ]) % M + A[i - ]) % M;
if (i > ) L[i] = ((A[i - ] + P[i - ]) % M + (A[i - ] + P[i - ]) % M) % M;
if (i > ) A[i] = ((A[i - ] + A[i - ]) % M + A[i - ]) % M;
}
return ((A[n - ] + P[n - ]) % M + L[n - ]) % M;
}
};

下面这种方法来自 大神 dettier 的帖子,这里面定义了两个数组P和 PorL,其中 P[i] 表示数组前i个数字中1以P结尾的排列个数,PorL[i] 表示数组前i个数字中已P或者L结尾的排列个数。这个解法的精髓是先不考虑字符A的情况,而是先把定义的这个数组先求出来,由于P字符可以再任意字符后面加上,所以 P[i] = PorL[i-1];而 PorL[i] 由两部分组成,P[i] + L[i],其中 P[i] 已经更新了,L[i] 只能当前一个字符是P,或者前一个字符是L且再前一个字符是P的时候加上,即为 P[i-1] + P[i-2],所以 PorL[i] = P[i] + P[i-1] + P[i-2]。

那么这里就已经把不包含A的情况求出来了,存在了 PorL[n] 中,下面就是要求包含一个A的情况,那么就得去除一个字符,从而给A留出位置。就相当于在数组的任意一个位置上加上A,数组就被分成左右两个部分了,而这两个部分当然就不能再有A了,实际上所有不包含A的情况都已经在数组 PorL 中计算过了,而分成的子数组的长度又不会大于原数组的长度,所以直接在 PorL 中取值就行了,两个子数组的排列个数相乘,然后再把所有分割的情况累加起来就是最终结果啦,参见代码如下:

解法三:

class Solution {
public:
int checkRecord(int n) {
int M = 1e9 + ;
vector<long> P(n + ), PorL(n + );
P[] = ; PorL[] = ; PorL[] = ;
for (int i = ; i <= n; ++i) {
P[i] = PorL[i - ];
if (i > ) PorL[i] = (P[i] + P[i - ] + P[i - ]) % M;
}
long res = PorL[n];
for (int i = ; i < n; ++i) {
long t = (PorL[i] * PorL[n - - i]) % M;
res = (res + t) % M;
}
return res;
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/552

类似题目:

Student Attendance Record I

参考资料:

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

https://leetcode.com/problems/student-attendance-record-ii/discuss/101638/Simple-Java-O(n)-solution

https://leetcode.com/problems/student-attendance-record-ii/discuss/101633/Improving-the-runtime-from-O(n)-to-O(log-n)

https://leetcode.com/problems/student-attendance-record-ii/discuss/101643/Share-my-O(n)-C%2B%2B-DP-solution-with-thinking-process-and-explanation

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Student Attendance Record II 学生出勤记录之二的更多相关文章

  1. [LeetCode] 552. Student Attendance Record II 学生出勤记录之二

    Given a positive integer n, return the number of all possible attendance records with length n, whic ...

  2. 552 Student Attendance Record II 学生出勤记录 II

    给定一个正整数 n,返回长度为 n 的所有可被视为可奖励的出勤记录的数量. 答案可能非常大,你只需返回结果mod 109 + 7的值.学生出勤记录是只包含以下三个字符的字符串:    1.'A' : ...

  3. [LeetCode] Student Attendance Record I 学生出勤记录之一

    You are given a string representing an attendance record for a student. The record only contains the ...

  4. Leetcode551.Student Attendance Record I学生出勤记录1

    给定一个字符串来代表一个学生的出勤纪录,这个纪录仅包含以下三个字符: 'A' : Absent,缺勤 'L' : Late,迟到 'P' : Present,到场 如果一个学生的出勤纪录中不超过一个' ...

  5. LeetCode 551. Student Attendance Record I (学生出勤纪录 I)

    You are given a string representing an attendance record for a student. The record only contains the ...

  6. 551 Student Attendance Record I 学生出勤纪录 I

    给定一个字符串来代表一个学生的出勤纪录,这个纪录仅包含以下三个字符:    'A' : Absent,缺勤    'L' : Late,迟到    'P' : Present,到场如果一个学生的出勤纪 ...

  7. [Swift]LeetCode552. 学生出勤记录 II | Student Attendance Record II

    Given a positive integer n, return the number of all possible attendance records with length n, whic ...

  8. 【leetcode】552. Student Attendance Record II

    题目如下: Given a positive integer n, return the number of all possible attendance records with length n ...

  9. 552. Student Attendance Record II

    Given a positive integer n, return the number of all possible attendance records with length n, whic ...

随机推荐

  1. JAVA多线程实现和应用总结

    1.JAVA多线程实现方式JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中 ...

  2. 密码脱落 JAVA 蓝桥杯

    密码脱落 X星球的考古学家发现了一批古代留下来的密码.这些密码是由A.B.C.D 四种植物的种子串成的序列.仔细分析发现,这些密码串当初应该是前后对称的(也就是我们说的镜像串).由于年代久远,其中许多 ...

  3. C#编程语言之委托与事件(一)—— C/C++函数指针和C#委托初步

    相信正在学习C#的人都有学习过C或C++的经验,本文要讲的第一个要点是C#中的委托(delegate,有些资料也叫代表).什么是委托,很多人都能自然而然地想到C/C++中的函数指针,事实上很多书和资料 ...

  4. Java基础学习笔记二十二 网络编程

    络通信协议 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样.在计算机网络中,这些连接和通信的规则 ...

  5. web服务器学习2---httpd-2.4.29虚拟目录及访问控制

    一 创建虚拟目录 环境准备: 系统:CentOS 7.4 软件:httpd-2.4.29 1.编辑主配置文件,添加命令运行子配置文件 vi /usr/local/httpd/conf/httpd.co ...

  6. New UWP Community Toolkit - RadialProgressBar

    概述 UWP Community Toolkit  中有一个圆形的进度条控件 - RadialProgressBar,本篇我们结合代码详细讲解  RadialProgressBar 的实现. Radi ...

  7. 第1次作业:小菜鸟的平凡IT梦

    #1.结缘计算机的始末 ##1.1与计算机相识的几年 作为一个95后,出生在一个互联网开始兴盛的时代.我记得小学的时候,开始知道电脑这个东西,学校有了机房,开始有了所谓的电脑课.那时候计算机对于我来说 ...

  8. alpha-咸鱼冲刺day1

    一,合照 emmmmm.自然是没有的. 二,项目燃尽图 三,项目进展 登陆界面随意写了一下.(明天用来做测试的) 把学姐给我的模板改成了自家的个人主页界面,侧边栏啥的都弄出来了(快撒花花!) 四,问题 ...

  9. NetFPGA-1G-CML从零开始环境配置

    NetFPGA-1G-CML从零开始环境配置 前言 偶得一块NetFPGA-1G-CML,跟着github对NetFPGA-1G-CML的入门指南,一步步把配置环境终于搭建起来,下面重新复现一下此过程 ...

  10. .NET Core装饰模式和.NET Core的Stream

    该文章综合了几本书的内容. 某咖啡店项目的解决方案 某咖啡店供应咖啡, 客户买咖啡的时候可以添加若干调味料, 最后要求算出总价钱. Beverage是所有咖啡饮料的抽象类, 里面的cost方法是抽象的 ...