动态规划
1. 概念
记忆化搜索:递归搜索 + 保存中间计算结果,如何思考状态转移是重点
递归:1:1 翻译 DFS 为 DP
三步走 🔥
- 思考回溯怎么写:① 入参是什么;② 递归到哪里;③ 注意递归边界和入口
- 改成记忆化搜索
- 1:1 翻译成递推:① dfs 改成 dp 数组;② 递归改成循环;③ 递归边界对应 dp 数组的初始化
时间复杂度:状态个数 * 单个状态计算时间
2. 解题技巧(我的总结)
1> 复杂问题无法一次性状态转移的可以分成多个子问题,分别进行状态转移,最后合并结果, (或者仅对其中一个子问题状态转移)
题目 | 说明 | 实现 |
---|---|---|
764. 最大加号标志 | 分成四个方向分别求解 | 我的提交 |
838. 推多米诺 | 分成两个个方向分别转移 | 我的提交 |
1031. 两个非重叠子数组的最大和 | 仅对(i:)区间内长度为firstlen和secondLen的最大长度状态转移 | 我的提交 |
1139. 最大的以 1 为边界的正方形 | 分别求解四个方向连续1的个数 | 我的提交 |
1525. 字符串的好分割数目 | 分别求解i左右边不同字符的个数 | 我的提交 |
1749. 任意子数组和的绝对值的最大值 | 分别求解子数组的最小和和最大和,简化状态转移 | 我的提交 |
1888. 使二进制字符串字符交替的最少反转次数 | 看成左右两个部分分别为交替串,从左到右、从右到左分别求解,细分成为0/1结尾的交替串两个子状态 | 我的提交 |
123. 买卖股票的最佳时机 III | 分成求解某一点左边卖出能获得的最大收益 + 某一点右边买入能获得的最大收益 之和 | 我的提交 |
2> 使用两个变量滚动记录 dp,优化空间
题目 | 说明 | 实现 |
---|---|---|
70. 爬楼梯 | 只记录上一个状态 | 我的提交 |
3> 一个状态划分两个或以上的子状态,分别对每个子状态进行转移,合并得到结果
题目 | 说明 | 实现 |
---|---|---|
357. 统计各位数字都不同的数字个数 | 划分是/否含0两个状态 | 我的提交 |
714. 买卖股票的最佳时机含手续费 | 划分是/否持有股票两个状态 | 我的提交 |
576. 出界的路径数 | 划分网格位置 m*n 个状态 | 我的提交 |
494. 目标和 | 划分当前和 1000 + 1 + 1000 个状态 | 我的提交 |
740. 删除并获得点数 | 排序后划分 是/否 选择当前数 2 个状态 | 我的提交 |
926. 将字符串翻转到单调递增 | 当前位分别为 ‘0’,‘1’ 时符合条件的最优值 | 我的提交 |
935. 骑士拨号器 | 长度为i的号码划分成从0~9开始的10个子状态,每个子状态之间通过马字形转移 | 我的提交 |
1155. 掷骰子等于目标和的方法数 | dp(i)(j)表示第i次累计和为j的种类数,1=<j<=target | 我的提交 |
1186. 删除一次得到子数组最大和 | dp(i)(0),dp(i)(1)分别表示前i个元素,删除和不删元素的最大子数组和 | 我的提交 |
1262. 可被三整除的最大和 | dp(i)(0),dp(i)(1), dp(i)(2)分别表示前i个元素%3的最大和 | 我的提交 |
1504. 统计全 1 子矩形 | dp(i,j,h) 表示右下端点为ij, 高度为h的矩形的最大宽度, dp(i,j,h) = dp(i,j-1,h) + 1 | 我的提交 |
1567. 乘积为正数的最长子数组长度 | dp(i)(0), dp(i)(1)分别表示i结尾乘积正/负数的最长子数组长度 | 我的提交 |
1594. 矩阵的最大非负积 | dp(i)(j)(0), dp(i)(j)(1)分别表示i,j位置累积非负/负的绝对值最大的乘积 | 我的提交 |
1638. 统计只差一个字符的子串数目 | dp(i)(j)(0), dp(i)(j)(1)分别表示以i,j位置结尾累积相同/差1字符的最长子串长度 | 我的提交 |
1824. 最少侧跳次数 | 划分成青蛙在三个跑道上的侧跳次数分别进行状态转移 | 我的提交 |
[188. 买卖股票的最佳时机 IV](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/description/ | 划分成持有股票和不持有两个自状态,对k和i进行 | [我的提交](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/submissions/487402940/ |
[1223. 掷骰子模拟](https://leetcode.cn/problems/dice-roll-simulation/description/ | 划分成6个数结尾,每个结尾再划分结尾连续相同数量(1~rollMax),分别进行状态转移 | [我的提交](https://leetcode.cn/problems/dice-roll-simulation/submissions/492484131/ |
4> 可对状态进行分类(26个字母等)、对数据进行排序等,从而大大减少时间
题目 | 说明 | 实现 |
---|---|---|
467. 环绕字符串中唯一的子字符串 | 针对26个字母分类巧妙去重 | 我的题解 |
2008. 出租车的最大盈利 | 根据到达地点从小到大排序 | 我的题解 |
940. 不同的子序列 II | 记录26个字母最后出现的位置,探索每个字符结尾的序列数目,不关心它们之间的重复,最终答案是26个字母最后出现位置的结尾序列数目之和 | 我的题解 |
2405. 子字符串的最优划分 | 记录26个字母倒数第二次出现的位置,每次取26个位置中最大的那个后面可以划分为一个 | 我的题解 |
5> 区间类型的动态规划
题目 | 说明 | 实现 |
---|---|---|
553. 最优除法 | 除法 = 左区间 / 右区间 | 我的提交 |
516. 最长回文子序列 | 当前区间 = 左端点 + 子区间 + 右端点 | 我的提交 |
1039. 多边形三角剖分的最低得分 | 当前区间 = 左区间 + 三角形 + 右区间 | 我的提交 |
1130. 叶值的最小代价生成树 | 当前区间最优 = min(左区间最优 + 右区间最优 + 左区间最大值*右区间最大值) | 我的提交 |
[312. 戳气球](https://leetcode.cn/problems/burst-balloons/description/ | (i,j)区间全部戳破分数 = score(i,mid) + score(mid,j) + num[mid]*num[i]*num[j] | 我的提交 |
[375. 猜数字大小 II](https://leetcode.cn/problems/guess-number-higher-or-lower-ii/description/ | (i,j)区间内确定的最小花费 = (i,mid-1) + (mid+1,j) + mid + 1 | 我的提交 |
[664. 奇怪的打印机](https://leetcode.cn/problems/strange-printer/description/ | 一定是优先打印两端的字符更好,两端字符不相等的话必然分成左右两个区间分别打印,两端相等则最后一个字符不用特地打印 | 我的提交 |
6> 根据题意使用多维动态规划
题目 | 说明 | 实现 |
---|---|---|
873. 最长的斐波那契子序列的长度 | 使用二维状态表示以i和j位置结尾的最长长度 | 我的提交 |
1027. 最长等差数列 | 使用二维状态表示以i和j位置结尾的最长长度 | 我的提交 |
87. 扰乱字符串 | 可以分解成子问题,s1、s2位置、长度三维动态规划 | 我的提交 |
474. 一和零 | dp(k,i,j) 表示前k个中限制0,1为i,j分别能得到的最大子集数目,集合问题一般就是考虑max(取当前元素后前面的最大值+1,不取当前元素时前面的最大值) | 我的提交 |
[1463. 摘樱桃 II](https://leetcode.cn/problems/cherry-pickup-ii/description/ | 三维动态规划,二维数组表示两个机器人位置 | 我的提交 |
7> 求概率问题从小规模下的概率递推到大规模条件的概率
题目 | 说明 | 实现 |
---|---|---|
808. 分汤 | 知道A=0&B=0的概率,A=0&B>0的概率,递推至A=N,B=N | 我的提交 |
8> 博弈游戏类型(当前用户最优选择 = max(当前选择 + 当前选择导致的状态下当前用户(即另一用户)最优选择))
题目 | 说明 | 实现 |
---|---|---|
486. 预测赢家 | 抽象成当前用户和下一用户之间的状态转移,利用区间类型动态规划 | 我的提交 |
464. 我能赢吗 | 类似的思路,使用记忆化搜索的方法 | 我的提交 |
1140. 石子游戏 II | dp(i)(j) 表示玩家在stones(i:)开始选择,M=j时能获得的最多分数 | 我的提交 |
2029. 石子游戏 IX | Loss() 表示当前玩家无论做任何选择都会输,则Loss返回False的前提是某个选择后对手Loss返回true | 我的提交 |
9> 当前状态可能由前面一个或多个特定状态转移得到,根据题目条件分析
题目 | 说明 | 实现 |
---|---|---|
823. 带因子的二叉树 | 排序数组,依次求每个节点为树根的情况 | 我的提交 |
907. 子数组的最小值之和 | i为右端点的所有子数组最小值之和 = 上一个更小元素(位置j)为右…之和 + arr[i]*(i-j) | 我的提交 |
983. 最低票价 | 第i天最低消费 = min( (1天票+前i-1天最低消费),(7天票+前i-7天最低消费),(30天票+前i-30天最低消费) ) | 我的提交 |
1024. 视频拼接 | 以每个片段结尾的消耗的片段 = 能和当前片段开头拼接上的所有片段结尾的片段数+1 (此题,易错点较多) | 我的提交 |
1048. 最长字符串链 | 先排序, 当前最长链 = max(删除每个字母后的新word在前面的链长 + 1) | 我的提交 |
1105. 填充书架 | 当前本之前的最优 = min(从当前本往前k本放在一行 + i-k之前的最优 ) (k=1,2…) | 我的提交 |
1578. 使绳子变成彩色的最短时间 | dp[i] = min(移除i + dp[i-1], 移除上一与i不同的j处的dp[j] + 移除前(i-j-1)的cost) | 我的提交 |
1621. 大小为 K 的不重叠线段的数目 | dp(i)(k) 表示i点前 分配 k段绳子的方法数 | 我的提交 |
1626. 无矛盾的最佳球队 | 对队伍双重排序(相同年龄按分数排),大大简化算法 | 我的提交 |
1696. 跳跃游戏 VI | 使用大顶堆优化对前面k个状态最优的查找 | 我的提交 |
1884. 鸡蛋掉落-两枚鸡蛋 | i楼时往第j层扔一个鸡蛋,碎了则N=1+(i-1), 没碎则N=[j+1:i]区间所需次数(即dp[0:i-j]) + 1,取二者较大值(从j=1开始不断增加即可) | 我的提交 |
1959. K 次调整数组大小浪费的最小总空间 | 划分成2段,总段在k情况最少使用空间 = 后一段的最大值 + 前一段在k-1情况下的最少使用空间 (后一段长度从1逐渐增大) | 我的提交 |
140. 单词拆分 II | 最后一个单词 + 前面的dp | 我的提交 |
446. 等差数列划分 II - 子序列 | 以j,k结尾的序列 可以由 前面特定若干个的i元素导致的 i,j结尾的序列数 + 1 | 我的提交 |
518. 零钱兑换 II | 前i个coin组合成amount的组合数由前i-1个coin组成amount-coin,amount-2*coin,…的组合数 转化而来 | 我的提交 |
795. 区间子数组个数 | 要同时记录以i结尾的不超过right的连续子数组数目,不然会漏掉很多解 | 我的提交 |
960. 删列造序 III | 即求一个字符串的最长上升序列动态规划问题,限制转移条件要对所有字符串满足即可 | 我的提交 |
1029. 两地调度 | 逐个遍历costs,记录前i个面试者在A地个数分别为minA,maxA的最小花费 | 我的提交 |
2054. 两个最好的不重叠活动 | 分成选择1个和选择2个的最大收益两个子状态分别根据最先满足endtime<starttime的结果转移 | 我的提交 |
410. 分割数组的最大值 | 划分为k个数组的最优值可以由:前面若干划分为k-1个 + 后面若干个为一组 的情况转变而来 | 我的提交 |
2266. 统计打字方案数 | 新状态 = 前面某个状态 + 最后一个字符,只需考虑dp[i] 可否由 dp[i-1], dp[i-2], dp[i-3], dp[i-4]转换而来 | 我的提交 |
10> 我称之为状态扩散,从某个状态按规则扩散到其余新状态
题目 | 说明 | 实现 |
---|---|---|
1162. 地图分析 | 某位置距离陆地d,则其上下左右的海洋距离陆地d+1,扩散至没有海洋 | 我的提交 |
1947. 最大兼容性评分和 | mark第j位表示第j个教授是否被选择,mark中有k个1,初始时mark=0,从每个mark扩散到含k+1的mark | 我的提交 |
11> 节点距离问题,使用动态规划、贝尔曼福特算法
每轮循环知道相邻 f -> t 之间的距离c,对其余节点i(i!=f && i!=t):
if c + d(t)(i) < d(f)(i) 则更新d(f)(i)
!!! 不要忘了还有: c + d(f)(i) < d(t)(i) 则更新d(t)(i)
题目 | 说明 | 实现 |
---|---|---|
1334. 阈值距离内邻居最少的城市 | 求出每个节点间最小距离 | 我的提交 |
1311. 获取你好友已观看的视频 | 求出每个好友间最小距离 | 我的提交 |
12> 状态压缩dp,通常用于数组A和数组B任意匹配问题
题目 | 说明 | 实现 |
---|---|---|
1947. 最大兼容性评分和 | mark第j位表示第j个教授是否被选择,mark中1的数量k表示现在为第k个学生匹配教授 | 我的提交 |
1986. 完成任务的最少工作时间段 | 子集型状态压缩 | 我的提交 |
2305. 公平分发饼干 | 子集型状态压缩 | 我的提交 |
13> 倒序动态规划
题目 | 说明 | 实现 |
---|---|---|
174. 地下城游戏 | dp(i,j)表示i,j到终点所需的最小初始值 | 我的提交 |
14> 线性dp
题目 | 说明 | 实现 |
---|---|---|
1269. 停在原地的方案数 | 记录各个位置的方案数,当前位置方案由上一位置±1/不变转换来 | 我的提交 |
1449. 数位成本和为目标值的最大数字 | 记录各个target的最大字符串,当前位置方案由上一位置+v转换来 | 我的提交 |
2400. 恰好移动 k 步到达某一位置的方法数目 | 线性dp,只需记录离终点距离k以内的数组(长度2k+1),不断减小k,newDp[i] = dp[i-1] + dp[i+1] | 我的提交 |
14> 数型dp
题目 | 说明 | 实现 |
---|---|---|
LCP 34. 二叉树染色 | 记录各个节点为根有连续i个节点的最大值(i取值从0到k),优化:i越小越有价值,dp[i]取前i个中的最大值 | 我的提交 |
3. 更多练习
题目 | 说明 | 题解 |
---|---|---|
1043. 分隔数组以得到最大和 | 枚举最后一段的子数组下标 j,dfs(i) 表示 arr[0…i] 做分隔变换得到的最大元素和 | 0x3F |
1105. 填充书架 | dfs(i) 表示摆放 books[0…i] 可以做到的最小高度,枚举最后一层可以放置的书 | 通过 |
1335. 工作计划的最低难度 | dfs(i, j) 表示 j 天完成 job[0…i] 的最小难度,有限制的枚举最后一个天的工作 job[k…i] 取最大值 mx | 通过 |
1416. 恢复数组 | dfs(i) 表示 s[0…i] 字符串分割成数组的方案数,枚举末尾段字符子串 t 可能得分割方式,不能含有前导 0 并且不能超过 k | 通过 |