说实话,dp一学就会,一做就懵的一个很重要的原因就是所谓递推不总是一件合乎思维的事。爬楼梯的题我们想象说dp[i]是爬后i级楼梯的种数,这样递推公式,数据结构,划分方法纷纷水到渠成,那像这样的题呢?
https://leetcode.cn/problems/student-attendance-record-ii/description/
可以用字符串表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:
‘A’:Absent,缺勤
‘L’:Late,迟到
‘P’:Present,到场
如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:
按 总出勤 计,学生缺勤(‘A’)严格 少于两天。
学生 不会 存在 连续 3 天或 连续 3 天以上的迟到(‘L’)记录。
给你一个整数 n ,表示出勤记录的长度(次数)。请你返回记录长度为 n 时,可能获得出勤奖励的记录情况 数量 。答案可能很大,所以返回对 109 + 7 取余 的结果。
我们或许可以很快的识别到这个大问题中包含着重复的子问题,那么,这是个几维的动态规划?它的每一个维度代表什么?状态转移方程怎么写?
这时候就体现了dfs转dp的重要。正着想是符合人类的思维的,递归转递推才是正路,直接从递推开始,太困难了。
先看看这个dfs怎么写。假设我们现在已经有一段确定的序列,当然这段序列还没有到n长,那么自然有三种将它延长的方式,加’A’、加’L’和加’P’。对于A,如果序列中已经有一个A了,那么再加A就不合适了,是0,否则继续;对于L,如果序列的后两位刚好都是L,那就组成了连续三个L,是不行的,返回0,否则继续;对于P,没什么触发禁忌的事,直接都继续
因此可以提炼出两个需要在dfs中用到的变量,已有序列中A的个数,已有序列末尾有连续几个L。手动再加一个dfs进行到第几层的深度变量,用于让这个递归停止
const dfs = (Acnt, tailLcnt, deep) => {
if (deep == n) return 1;
con