一、问题描述
可以用字符串表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:
‘A’:Absent,缺勤
‘L’:Late,迟到
‘P’:Present,到场

如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:
按总出勤 计,学生缺勤(‘A’)严格 少于两天
学生不会存在 连续 3 天或 连续 3 天以上的迟到(‘L’)记录。

给你一个整数 n ,表示出勤记录的长度(次数)。请你返回记录长度为 n 时,可能获得出勤奖励的记录情况 数量 。答案可能很大,所以返回对 10的9次方加7取余的结果。

二、问题分析
自己想用数学分析的方法做,很明显自己推了很多,但是情况还是很复杂,特别是当A和连续的L在一起的时候,问题变得很复杂。
后来又想到用分治,结果是一样得,子问题划分上不好划分,并且原问题不能单纯由子问题构成。

三、记忆化搜索
枚举所有方案得暴力搜索DFS(深度优先搜索)
设计变量

u 当前还剩下多少位需要决策 ,其实可以看成还有几天的出勤情况没算
acnt 当前方案中A的出现总次数 ,acnt小于2的时候,是可能拿到奖金的
lant 当前方案中结尾L的总出现次数,lant连续出现0,1,2,是有可能拿到奖金
cur 当前方案,看成当前出勤天数下的出勤情况
ans 结果集,当前的出勤天数下的能拿到到奖金的情况数

最开始的笨方法当然是暴力搜索,不过要采用回溯的思路,每次加一天的出勤情况,有三种可能,分别判断,如果满足能拿奖金的情况,那么情况数就增加;这样一直加直到满足n,u其实是每次都在减少的。
回溯的解决问题的一般步骤是:
1.针对给定问题,定义问题的解空间,它至少包含问题的一个解
在本题中,拿将金就是一个解情况,满足拿奖金的条件设计就是在定义问题的解空间。
2.确定易于搜索的解空间结构,使得回溯法能够方便地搜索整个解空间
然后每次加一天,然后看看能拿到奖金的情况
3.以深度优先的方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索
比如当添加一天后不能拿到奖金了,那么这种情况直接就抛弃了,后面从新从能拿将金的情况里选一个继续进行。
设计函数
1.如果新的一天是P,那么可以直接方进来,并把这种情况方入解空间
2.如果新的一天是A,那么只有之前天里A没出现,即acnt<1时,可以把这种情况方入解空间
3.如果新的一天是L,那么L只能连续出现两个,上一天是P或A都是可以的,即lant<2
dfs(int day,int n,int absent,int late)
其中day表示第几天,n表示一共的天数,absent表示当天的出勤情况,late表示最多的连续迟到天数。

//初始化输入
public int checkRecord(int n){return dfs(0,n,0,0)
}
//函数主题逻辑
private int dfs(int day,int n,int absent,int late){//退出条件if(day>=n){return 1;}//回溯开始int ans=0;//1.新的一天是Pans=ans+dfs(day+1,n,absent,0)//2.新的一天是Aif(absent<1)ans=ans+dfs(day+1,n,1,0)//3.新的一天是Lif(late<2)ans=ans+dfs(day+1,n,absent,late+1)return ans;
}
//最终返回的是n天的情况数

时间复杂度:O(3^n),n个位置,每个位置有3个选择,就是33…*3
空间复杂度:O(n),递归层数为 n。

实际上,我们不需要枚举所有的方案数,因此我们只需要保留函数签名中的前三个参数即可。
同时由于我们计算某个(u,acnt,lant)的方案数时,其依赖的状态可能会被重复使用,考虑加入记忆化,将结果缓存起来。其实就是记住前一天的情况,通过分析把收索次数降下来。
例如:我们要计算 day=2, absent=1, late=0,它有可能从哪些状态而来呢?
absent=1可能是第 0 天填入的;
absent=1可能是第 1 天填入的;
所以,以上两种情况,到第 2 天的时候的状态是完全一样的,也就产生了重复计算,所以,我们可以声明一个缓存,记录每个状态下计算得到的值,下次再遇到相同的状态,直接返回即可。
看代码

public int checkRecord(int n){int[][][]memo=new int[n][2][3];return dfs(0,n,0,0,memo);
}
private int dfs(int day, int n, int absent, int late, int[][][] memo) {if (day >= n) {return 1;}// 相同的状态计算过了直接返回if (memo[day][absent][late] != 0) {return memo[day][absent][late];}// 回溯开始int ans = 0;// 1. Present随便放ans = ans + dfs(day + 1, n, absent, 0, memo);// 2. Absent最多只能放一个if (absent < 1) {ans = ans + dfs(day + 1, n, 1, 0, memo);}// 3. Late最多连续放2个if (late < 2) {ans = ans + dfs(day + 1, n, absent, late + 1, memo);}// 记录每一个状态的计算结果memo[day][absent][late] = ans;return ans;}

时间复杂度:O(n),通过memo可以看到有 n * 2 * 3种状态,每个状态只会计算一遍,所以是 6n,时间复杂度为 O(n)。
空间复杂度:O(n),递归层数为 n,memo数组占用 n * 2 * 3 = 6n的空间,两者空间复杂度都是 O(n)。

四、动态规划
好了,有了记忆化,转 DP 就非常简单了,只要把 memo 改成 dp 即可,我们这样定义动态规划:
状态定义:dp[i][j][k]表示第 i 天、在 A 为 j 次、连续的 L 为 k 次的方案数。
状态转移:所有的状态都是从前一天,即 i-1,转移而来,但是对于 j 和 k,

要分三种情况来讨论:
当前填入的是 P,i-1 天的任何状态都能转移过来;
当前填入的是 A,i-1 天即之前肯定没填过 A,同时所有的 late 状态都可以转移到过来。
当前填入的是 L,i-1 天最多只能有一个连续的 L,其他的状态依次转移过来。

为了方便计算,我们把第 0 天的值初始化。
好了,请看代码,加了详细注释:

class Solution {int MOD = 1000000007;public int checkRecord(int n) {long[][][] dp = new long[n][2][3];// 初始值dp[0][0][0] = 1;dp[0][1][0] = 1;dp[0][0][1] = 1;for (int i = 1; i < n; i++) {// 本次填入P,分成前一天累计了0个A和1个A两种情况dp[i][0][0] = (dp[i - 1][0][0] + dp[i - 1][0][1] + dp[i - 1][0][2]) % MOD;dp[i][1][0] = (dp[i - 1][1][0] + dp[i - 1][1][1] + dp[i - 1][1][2]) % MOD;// 本次填入A,前一天没有累计A都能转移过来// 这行可以与上面一行合并计算,为了方便理解,我们分开,下面会合并dp[i][1][0] = (dp[i][1][0] + dp[i - 1][0][0] + dp[i - 1][0][1] + dp[i - 1][0][2]) % MOD;// 本次填入L,前一天最多只有一个连续的L,分成四种情况dp[i][0][1] = dp[i - 1][0][0];dp[i][0][2] = dp[i - 1][0][1];dp[i][1][1] = dp[i - 1][1][0];dp[i][1][2] = dp[i - 1][1][1];}// 计算结果,即最后一天的所有状态相加long ans = 0;for (int i = 0; i < 2; i++) {for (int j = 0; j < 3; j++) {ans = (ans + dp[n - 1][i][j]) % MOD;}}return (int) ans;}
}

五、矩阵快速幂
六、总结
这个题最开始想到的是用回溯的思想,递归的深度优先遍历求解答案,但是递归的次数过多,然后有很多情况没有加以利用,效率很慢。
然后就用记忆化的方法,保存每次的状态,如果后续能用到这种状态,直接调用,就不再递归了,这样一下就把时间复杂度降低了。
现在看代码还是有点迷糊,还是先写倒这吧。

算法第四题:学生出勤记录情况统计 2021-08-19相关推荐

  1. Leetcode每日一题-学生出勤记录 II(Student Attendance Record II)

    可以用字符串表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤.迟到.到场).记录中只含下面三种字符: 'A':Absent,缺勤 'L':Late,迟到 'P':Present,到场 ...

  2. 字符串题目:学生出勤记录 I

    文章目录 题目 标题和出处 难度 题目描述 要求 示例 数据范围 解法 思路和算法 代码 复杂度分析 题目 标题和出处 标题:学生出勤记录 I 出处:551. 学生出勤记录 I 难度 2 级 题目描述 ...

  3. C++Python描述 LeetCode 551. 学生出勤记录 I

    C++&Python描述 LeetCode 551. 学生出勤记录 I   大家好,我是亓官劼(qí guān jié ),在公众号.CSDN.GitHub.B站.华为开发者论坛等平台分享一些 ...

  4. 552. 学生出勤记录 II

    552. 学生出勤记录 II 可以用字符串表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤.迟到.到场).记录中只含下面三种字符: 'A':Absent,缺勤 'L':Late,迟到 ...

  5. 551. 学生出勤记录

    551. 学生出勤记录 I 给你一个字符串 s 表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤.迟到.到场).记录中只含下面三种字符: 'A':Absent,缺勤 'L':Late ...

  6. LeetCode 552. 学生出勤记录 II(动态规划)

    文章目录 1. 题目 2. 解题 1. 题目 给定一个正整数 n,返回长度为 n 的所有可被视为可奖励的出勤记录的数量. 答案可能非常大,你只需返回结果mod 10^9 + 7的值. 学生出勤记录是只 ...

  7. 力扣 -- 551. 学生出勤记录 I 、 552. 学生出勤记录 II

    目录 551. 学生出勤记录 一 .题目描述 二. 实现思路以及代码 552. 学生出勤记录 II 一 .题目描述 二. 实现思路以及代码 551. 学生出勤记录 一 .题目描述 给你一个字符串 s  ...

  8. LeetCode——552. 学生出勤记录 II(Student Attendance Record II)[困难]——分析及代码(Java)

    LeetCode--552. 学生出勤记录 II[Student Attendance Record II][困难]--分析及代码[Java] 一.题目 二.分析及代码 1. 动态规划 (1)思路 ( ...

  9. leetcode: 552. 学生出勤记录 II

    552. 学生出勤记录 II 来源:力扣(LeetCode) 链接: https://leetcode.cn/problems/student-attendance-record-ii/ 可以用字符串 ...

最新文章

  1. Linux下vi和vim模式相互切换
  2. tf.argsort
  3. 智能车的转弯部分_江西智能搬运平板车铁路轨道运输车-厂家直销
  4. java 开发银行支付、对账时证书相关的操作总结
  5. photorec_如何在Linux / Ubuntu中使用PhotoRec恢复已删除的文件
  6. linux redis-连接命令
  7. 【地震数据处理】GAN网络基础知识
  8. 傅里叶变换 FFT 频谱泄漏?
  9. L2-016 愿天下有情人都是失散多年的兄妹 (25 分) fill函数、bfs
  10. C#:Winform 打字测速程序 Typer
  11. Landscape Photography: Winter 风景摄影:冬天 Lynda课程中文字幕
  12. mysqladmin 管理命令详细应用
  13. 无法使用备份文件 'D:\20160512.bak',因为原先格式化该文件时所用扇区大小为 512,而目前所在设备的扇区大小为 4096...
  14. KITTI数据集去除camera生成bag包(kitti2bag)
  15. 论斯芬克司吃人的合理性。
  16. 【字符串比较函数】strcmp,stricmp,strcmpi区别
  17. 用友修改了变量不能连接到服务器,用友T3客户端登录提示 运行时错误91:未设置对象变量...
  18. PLC实现入栈出栈功能
  19. 浏览器内核WebKit编年史
  20. nginx实现ip端口转发_nginx 怎么做端口转发

热门文章

  1. 信号量与生产者消费者问题
  2. C++STL常用操作之prev、next篇
  3. android 电话回音消除,智能门铃中可视对讲的回音消除
  4. 服务器 硬盘 2.5改3.5,教你如何把2.5寸硬盘当3.5寸硬盘使用
  5. 2019年自考计算机应用基础(实践),2019年自考计算机应用基础试题练习(九)
  6. 算法设计与分析——动态规划(一)矩阵连乘
  7. html天气插件iframe,HTML_利用iframe在网页中显示天气附效果截图,css: 复制代码代码如下: *{margi - phpStudy...
  8. 华为云 承诺重于泰山!
  9. ffmpeg 设置网络代理_MAC下使用SSH设置代理的办法
  10. 英语学习的方法(1)