Dynamic Programming Problem Notes
2024-03-21 23:24:45
# Algorithm
动态规划问题
判断是否能使用dp:
- 一个状态是由前面的某个状态或某些状态得到的,且不用关心前面的这些状态是如何得来的。
- 确定
dp[i][j][k]..[l]
,就是确定i,j,k,l
这些状态是否能通过前面的i,j,k,l
的来得到。因此可以定义出dp函数的定义、应该是几纬和动态转移方程。 - 另一种定义状态的方式是看你要当前的答案需要关注哪些状态,哪些状态会影响到当前的答案(如股票问题的dp状态方程定义)
- 通常是求最值、方法数
记忆化搜索
模版
1 | int[][] memo = new int[n]; |
转移为递推须知
- 快速边界修改:
- 转换递推后如果出现
memo[i][j-1]
的情况,可以把整个第二维度(j)+1
,注意第一维度(i)不用变化。相当于是把整个数组往后挪了一位。 - 出现
memo[m-1][n-1]
我们都统一+1
,变成memo[i][j]
,memo[i][j-1]
,memo[m-1][n]
- 这种方法不需要修改for loop的index,也不需要做特殊判断
- 如果是数字的,也要修改!!比如在递归里面我们是判断
if(i<0){return 0}
,正常会写成memo[-1]
,但因为我们将数组扩了1位,因此这个地方需要变成memo[0]
- 一些特殊情况: 在初始化的时候,我们要注意看for loop里的值是从哪里开始循环的,如果本来就是
i=0
开始的,其实就不需要写成i+1
了 - 简单来说就是把数组退后了一位,其他所有地方都要看情况修改。还有一种情况就是
memo[i+1][j+1]
的话,可以把数组扩大1位,但是不用修改i
的值,因为我们不需要挪动数组。
- 转换递推后如果出现
1 | /** 记忆化例子 **/ |
- 转换成一维也有需要注意的地方
- 当
memo[i][0]
变成了memo[0]
时,我们需要在下面dp的i的for循环
下进行更新,因为这样才能保证每一步都有正确的值(否则会被上一次的更新覆盖掉)
- 当
一维例子:
1 | /** 原来的二维 **/ |
背包问题
从一个数组中,或者从一个固定范围的元素中,取出一些没有必要连续的元素,让他们的和【恰好】【至多】【至少】为k
0/1背包
背包容量为capacity,给一堆物品,每个物品的重量为w[i]
,问你恰好装满整个背包的话能有多少【方案数】
1 | # 本质上是选/不选的问题,且每个物品只能被选一次 |
注意【恰好】可以改成至多、至少,这些直接在递归入口进行逻辑判断
完全背包
背包容量为capacity,给一堆物品,每个物品的重量为w[i]
,问你恰好装满整个背包的话能有多少【方案数】(每种物品可以选无数次)
1 | # 本质上是选/不选的问题,且每个物品每一轮都可以被选 |
最长公共子序列(LCS)
简称LCS
1 | # 本质上是选/不选的问题 |
类似习题
- 1458. Max Dot Product of Two Subsequences
- LCS问题中,我们寻找的是长度,每个匹配项贡献的是固定值(即
+1
),因此不需要考虑“只选择当前i
和j
”的情况,因为长度的增加总是基于两个序列中相对应位置的元素匹配。 - 最大点积子序列问题中,我们寻找的是最大化数值(点积),这个值是变化的,取决于具体的元素值。因此,考虑“只选当前的
i
和j
”是有意义的,因为它们单独的点积可能就是最大的,不需要依赖于它们之前的元素。
- LCS问题中,我们寻找的是长度,每个匹配项贡献的是固定值(即
编辑距离
1 | # 删除、插入、替换的操作次数 |
最长递增子序列(LIS)
选出来的递增子序列必须是严格递增的,通常有两种方法
- dp
1 | int[] dp = new int[n]; |
- 贪心+二分
- 在java中我们维护一个数组
arr
,数组需要保证严格递增,假设j
为数组arr
目前的最后一个元素,如果nums[i]>arr[j]
,则直接加入数组中,相反使用二分法找到第一个大于nums[i]
的数,并将其替换。 - 这个方法依赖于贪心策略:
使得长度为i+1的数组中的最后一个元素最小
,这样才有可能构造更长的子序列 - 最后答案为
j+1
=>这个j+1
是代表整个数组中的最长递增子序列,不代表是nums[i]
为最后一个元素的最长递增子序列! - 如果要更新
i
结尾元素的最长递增子序列,要根据nums[i]
所被替换或插入的index来
- 在java中我们维护一个数组
这类题型可以有很多变形,最常见的就是值递增,或通过排序后满足某种特定的“有一致性、传递性的条件”来求最大长度等等
俄罗斯套娃问题
- 假设
m
为宽度,n
为高度,按宽度排序,宽度相同时按高度下降排序,这样做的好处是我们只比较高度n
时,能够避免将相同宽度的信封算进去。 - 相似问题
- 1626. Best Team With No Conflicts
- 虽然这道题没有很明显的递增,但是我们也是有条件可以看出来递增的:当一名年龄较小球员的分数严格大于一名年龄较大的球员,则存在矛盾。 所以,在按年龄排序的情况下,score要递增;在按score排序的情况下,年龄要递增。这就是我们判断他是否“递增”的条件。
- 1626. Best Team With No Conflicts
股票系列问题
简单来说就是状态机DP
- 持有/未持有
- 第i天
- 第k次交易
1 | /** 这是没有冷冻期的情况 **/ |
- 在Leetcode 121中,我们被要求最多只能交易一次,这实际上是和最多交易
k
次的模版一样的
区间DP
本质上也是选与不选的问题,通常与palindrome、分割区间有关
分割区间的小tip
- 1547. Minimum Cost to Cut a Stick
- 可以将分割点数组直接看做一个完整的stick,即排序后新建一个数组,从而简化dp