动态规划
借一下代码随想录的图
01背包
[416] 分割等和子集
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
解法
转换成01背包,背包的容量是sum / 2,要放入的物品的重量为nums数组元素的数值,价值也是其数值。如果背包正好装满则说明找到了总和为sum / 2的子集。
- dp数组的含义 => dp[i]表示容量为j的背包可装入的最大价值为dp[j]
如果背包容量为target, dp[target]就是装满 背包之后的重量,所以 当 dp[target] == target 的时候,背包就装满了。
- 状态转移方程 => dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
- 初始化 => dp[0] = 0;
- 遍历顺序 => 外层物品内层背包容量,外层顺序内层倒序
1 | // 模板 |
代码
1 | class Solution { |
[1049] 最后一块石头的重量Ⅱ
有一堆石头,用整数数组 stones
表示。其中 stones[i]
表示第 i
块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x
和 y
,且 x <= y
。那么粉碎的可能结果如下:
- 如果
x == y
,那么两块石头都会被完全粉碎; - 如果
x != y
,那么重量为x
的石头将会完全粉碎,而重量为y
的石头新重量为y-x
。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0
。
解法
一堆正的一堆负的,即一堆大的一堆小的。尽量两堆的重量相等所剩的质量才最小。
$\sum \limits ^{n-1} _{i=0}k_i ·stones[i] = (sum - neg) - neg = sum - 2 · neg$
要使最后一块石头的重量尽可能地小,$\textit{neg}$ 需要在不超过 $\textit{sum}/2 $ 的前提下尽可能地大。因此本问题可以看作是背包容量为 $\textit{sum}/2 $,物品重量和价值均为 $\textit{stones}[i]$的 0-1 背包问题。
代码
1 | int sum = accumulate(stones.begin(), stones.end(), 0); |
[494] 目标和
给你一个非负整数数组 nums
和一个整数 target
。
向数组中的每个整数前添加 '+'
或 '-'
,然后串联起所有整数,可以构造一个 表达式 :
- 例如,
nums = [2, 1]
,可以在2
之前添加'+'
,在1
之前添加'-'
,然后串联起来得到表达式"+2-1"
。
返回可以通过上述方法构造的、运算结果等于 target
的不同 表达式 的数目。
解法
target = pos - neg = sum - 2 * neg
neg = (sum - target) / 2 => 背包容量
由于0 <= sum(nums[i]) <= 1000
,-1000 <= target <= 1000
,所以neg是非负整数,所以上式成立的前提是 sum - target 是非负偶数。
若上式成立,问题转化成在数组nums中选取若干元素,使得这些元素之和等于 neg(装满背包),计算选取元素的方案数,可以使用动态规划的方法求解。
dp[j] 代表填满容量为j的背包的方案数,只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法。
代码
1 | class Solution { |
[474] 一和零
给你一个二进制字符串数组 strs
和两个整数 m
和 n
。
请你找出并返回 strs
的最大子集的长度,该子集中 最多 有 m
个 0
和 n
个 1
。
如果 x
的所有元素也是 y
的元素,集合 x
是集合 y
的 子集 。
解法
求最大子集的长度,这里的物品是指每个strs里面的字符串(strs的类型为vector<string>
),背包的容量是m和n,每个字符串的重量是其字符串中字符0和字符1的数量,每个字符串的价值为1。
注意这题不是完全背包,而是01背包,因为每个字符串只能用一次。
代码
1 | class Solution { |
完全背包
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。而完全背包的物品是可以添加多次的,所以要从小到大去遍历,即:
1
2
3
4
5
6 // 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}组合数:先物品后容量;
排列数:先容量后物品。
[518] 零钱兑换Ⅱ
题目
代码
1 | class Solution { |
[377] 组合总和Ⅳ
题目
代码
正整数且不存在重复数字,找出和为目标整数的排列个数,可以用回溯暴力搜
这里的数字又可以重复使用,而且只要排列的个数不需要将每个答案都列出来,因此可以用动态规划进行优化。是一个完全背包问题。
物品是数组里的元素,背包的重量和价值是元素之和,背包容量是target。
dp[j] 表示目前重量为j的背包里装的方法排列个数,因此 dp[0]
要初始化为1,虽然 dp[0] = 1
是没有任何意义的,只是为了进行递推。
排列数外层是背包重量,且内层从小到大;内层是物品。
C++测试用例有两个数相加超过int的数据,所以需要在if里加上dp[j] < INT_MAX - dp[j - nums[i]]。
1 | int n = nums.size(); |
[70] 完全爬楼梯
题目
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬至多m (1 <= m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
输入描述:输入共一行,包含两个正整数,分别表示n, m
输出描述:输出一个整数,表示爬到楼顶的方法数。
代码
物品是 1 ~ m
共m个整数,背包容量是n,且是排列数
1 |
|