本文章还未完成最终梳理,内容持续更新中。
状态转移定义
- 以i为结尾且i必被用上:
什么情况下用?被选中用起来的地方(也就是解)之间需要满足某种顺序或连续性约束(为了满足这种约束,当下一轮【暂时解】不满足约束时,可以直接另起炉灶,目前看到的都是max/min,而无加减)
如300.最长递增子序列,【暂时解】中每一位置严格顺序递增
若属于该情况,则当求最长/短时,最终结果是max/min(dp) - 只是以i为结尾而i未必用上:
什么情况下用?不需要满足顺序约束(目前看到的普遍包含加减)
如70.爬楼梯,【暂时解】不存在约束,仅是在统计路线数量;甚至当dp为二维时,如516.最长回文子序列和5.最长回文子串,【暂时解】中每一位置是与其对称位置相同,而非存在顺序依赖,也是因此才需要二维。
若属于该情况,则当求最长/短时,最终结果是dp[n]
维度数
- 二维dp[i][j]:
什么情况下用?
① 两个作用平等的串/序列之间比较且允许固定一侧,被允许固定一侧的原因是一旦当前状态【暂时解】确定,后续决策仅与当前状态相关,而与如何到达该状态的路径无关,这种情况也被称为无记忆性(=Markov property)。
如72.编辑距离,i和j分别代表两串走到哪个位置,由于这题是走到哪个位置则【暂时解】包含到哪里,将一个串增删改为另一个串(也就是解了),所以直接属于“而i未必用上”的情况(且无约束关系),此外若只有改则只要dp[i],但有增删,那长度就有变化,就要用二维了;
② 一个串上取左边界和右边界,其原因就是存在记忆性
如516.最长回文子序列和5.最长回文子串,【暂时解】中每一位置是与其对称位置相同,需要付出代价来记住左边界,因此需要二维记住左边界和右边界。 - 一维dp[i]:
什么情况下用?
① 在一个串上允许固定一侧
如139.单词拆分,两串的地位不均等,主串1个、辅串多个,辅串实际上只是被检索的资源而已,并不需要每次都重新计算,所以此处仍属于一个串的情况,同时因为最右端实际上不存在与左侧任意某一位置之间的依赖,因此可以从二维降维到一维(目前只提交了二维的代码版本)。
② 一个串上无记忆性
状态转移方向
- 逻辑上适合从左向右,实现上直接从左向右即可
最常见的情况,当前状态可以很容易地确定来自过去(左侧)哪些状态
1. 直接从左向右,采用灵荼的查表法解题:0到n遍历到i时,查询过去(左侧)状态并组合而成当前状态的最终值 - 逻辑上适合从右向左,实现上可以直接从右向左,但也可以从左向右,但需要改变思路使用技巧,不然会非常难写
当前状态可以很容易地确定影响未来(右侧)哪些状态
1. 直接从右向左,n到0遍历到i时,查询过去(右侧)状态并组合而成当前状态的最终值
2. 从左向右,采用灵荼的刷表法解题:0到n遍历到i时,最终确定下当前状态,更新未来状态初始值
状态初始化边界
直接从递推式看,把用到的索引想成一个矩形或序列,来考虑初始化才能保证不漏。
示例:当递推式是 dp[i, j] <- dp[i+1, j-1]时,相当于是在一个 2*2 小矩阵中,要从左下角推到右上角,因此边界条件要考虑到要让贴边的每个值(作为右上角)所处的这个 2*2 矩阵里其他值都有初始值,此题就是在 i=i 那一条的基础上再铺一层,不然会有从斜对角缝里漏出去的
内部结构
- 哈希表
- 使用条件:检索目标是值而非索引
- 示例:如题 1218. 最长定差子序列,dp 是个字典,根据当前值进行一番计算后找下一个离的最近的某项,dp 问题就不再用列表实现而改成用字典实现了。如1027.最长等差数列,dp是个字典列表。
dp = defaultdict(int)
dp[i]=最优子结构···dp[i-1]···
最后取dp.values()···
以上所谓的依赖就是用判断条件来实现


