动态规划
动态规划的核心:状态定义,状态转移方程
动态规划的想法
怎么把这两个东西想出来。
启发思路,回溯中子集型回溯,对于一个元素有选或不选两种思路。一些动态规划问题也可以用这两种思路来思考。
递归
打家劫舍。用递归的想法来考虑,问题的分解
思考过程:对于打家劫舍,可以先从边界开始思考,第一个或者最后一个的约束比较小,如果第 n 个不选,那么问题就变成了剩下 n - 1 个房子选或不选的问题。如果第 n 个选,那么问题就变成了剩下的 n - 2 个房子的问题。不断地重复下去。
回溯三问:
当前操作是什么?枚举第 i 个选/不选
子问题是什么?从前 i 个房子中获得最大金额和
下一个子问题?
选:从前 i - 2 个中获得最大金额和
不选:从前 i - 1 个中获得最大金额和
dfs(i) = max(dfs(i - 1), dfs(i - 2) + nums[i])
写程序之前想好入口参数和返回值是什么。回溯的时间负责度是指数级别,会超时,因此考虑优化。
记忆化搜索
递归搜索 + 保存计算结果 = 记忆化搜索。注意到 dfs 的计算,有时会传递进去相同的参数,但是还要展开计算。因此可以考虑记录一下计算值存储到 cache 数组。
这样的话递归的次数和节点数就是一样的,时间复杂度为 O(n)
优化后,时间复杂度和空间复杂度都是 O(n)
递推
算法中计算 max 发生在 dfs 函数调用结束后,也就是递归的归中才实际计算 max。
查看搜索树的话,使能看出从哪些点归回去的,那么就去掉递归中的递,只留下归。从下往上计算。
自上向下计算为记忆化搜索,自下向上计算就是递推。
记忆化搜索向递推的变换。
dfs(i) = max(dfs(i - 1), dfs(i - 2) + nums[i])
f[i] = max(f[i-1], f[i-2] + nums[i])
函数变数组,递归变循环。
这样的优化空间复杂度还是 O(n) 还可以接着优化。
f[i] 是现在要算的,f[i-1] 是上一个算出来的,f[i-2] 是上上个算出来的。也就是说,计算当前状态的,只需要知道上个状态的和上上个状态的,并不需要一整个数组。
0-1 背包问题
0-1 背包和完全背包是比较重要的 DP 模型,是选或不选思想的代表。
0-1 背包问题:
有 n 个物品,第 i 个物品的体积为 w[i],价值为 v[i] ,每个物品最多选一个,求体积为 capacity 的背包能装下的最大价值是多少。
从递归开始思考
此问题的思考
当前操作:第
i个物品选或不选。选了容量变小,不选容量不变。确定递归参数 i c当前问题?剩余容量为
c时,前i个物品价值最大下一个问题?
第
i个选了:剩余容量为c - w[i]时,从前i - 1个物品价值最大第
i个不选:剩余容量为c时,从前i - 1个物品价值最大
递归表达式
代码实现
练习一下这个思路,leetcode 494 目标和。思考过程:把这个问题转化为选一些数添加负号满足一个值的背包问题。所有数和为 sum ,添加正号的数的和为 p,添加负号的数和为 q,有 p + q == sum,根据题目有 p - q = target,那么就有 2*p - sum = target,那么 p == (target + sum)/2 。通过一些数学上的计算,问题转化成从 nums 选一些数字,使得和为 (target + num)/2。
常见的变形问题:
1.最多装
c,求方案数、最大价值和2.正好装
c,求方案数、最大价值和、最小价值和3.最少装
c,求方案数、最小价值和
目标和这个题目转化后是正好装 c 的问题,要做的是把那个表达式改为加法。可以这么做的原因:选和不选两件事是互斥的,事件 A 或事件 B 发生的总数等于事件 A 的数量加上事件 B 的数量。
代码实现
递推实现
去优化空间复杂度,递归改递推
使用了 C99 的 VLA 特性。也可以申请内存。
关于细节的处理,数组下标,边界条件转化为数组初始条件。
此二维数组的含义,表示从前 i 个数字中,选取若干个数字,使得它们的和等于 j 的方案数。
i 表示前 i 个数字:i 的范围是从 0 到 numsSize。dp[i][j] 表示使用数组 nums 中的前 i 个数字(即 nums[0], nums[1], ..., nums[i-1])去构造和为 j 的组合方案数。
j 表示背包容量:j 的范围是从 0 到 m,表示通过这些数字能够构造出的和。
空间优化:两个数组
空间优化:一个数组
完全背包
完全背包问题:
有 n 种物品,第 i 种物品的体积为 w[i],价值为 v[i] ,每种物品无限次选,求体积不超过 capacity 时的最大价值是多少。
递归三问
当前操作:第
i种物品选或不选。选一个容量变小w[i],不选容量不变。确定递归参数 i c当前问题?剩余容量为
c时,前i种物品价值最大下一个问题?
第
i种物品选一个:剩余容量为c - w[i]时,从前i - 1种物品价值最大第
i种物品不选:剩余容量为c时,从前i - 1种物品价值最大
和 0-1 背包的区别,在选完一个物品以后,i 是不变的,还可以接着选。
leetcode 322 零钱兑换。这是完全背包的一个变形
完全背包问题的常见变形
至多装 capacity,求方案数、最大价值和
恰好装 capacity,求方案数、最大价值和、最小价值和
至少装 capacity,求方案数,最小价值和
递归实现
根据题目,要做适当的考虑
对比这几个题目,要考虑返回值的问题,通常吧结果作为返回值。但是在边界判断的时候的常用技巧, C 种预定义的最大最小值,或者非负数的话把 -1 作为不满足条件的返回值。这里把 LLONG_MAX 作为没有合法方案的返回值。
递推实现,两个数组
上面的代码在 leetcode 里是超时的。
空间优化,两个数组和一个数组
变形问题总结
常见的变形问题:
1.最多装
c,求方案数、最大价值和2.正好装
c,求方案数、最大价值和、最小价值和3.最少装
c,求方案数、最小价值和
主要是边界条件的确定。
最后更新于