0-1背包理论基础
0-1背包问题介绍
0-1背包问题:有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
0-1背包问题可以使用回溯法进行暴力求解,指数级别的时间复杂度。所以才需要动态规划的解法来进行优化!
举例说明:
背包最大重量为4。
物品为:
重量 | 价值 | |
---|---|---|
物品0 | 1 | 15 |
物品1 | 3 | 20 |
物品2 | 4 | 30 |
问背包能背的物品最大价值是多少?
下面围绕该例子展开动态规划如何求解0-1背包问题。
二维dp数组解决0-1背包问题
依然动规五步曲分析一波。详情见代码随想录
一维dp数组(滚动数组)解决0-1背包问题
对于背包问题其实状态都是可以压缩的。详情见代码随想录
416. 分割等和子集
题目链接:416. 分割等和子集
思路:使用0-1背包问题,只有确定了以下几点,才能够将0-1背包应用到这个问题上来。
- 背包的体积为sum / 2
- 背包要放入的商品(集合里的元素)重量为元素的数值,价值也为元素的数值
- 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
- 背包中每一个元素是不可重复放入的。
动规五步曲分析如下:
-
0-1背包中,dp[j]:容量为j的背包,所背的物品价值最大可以为dp[j]。
本题中每一个元素的数值既是重量,也是价值。套到本题,dp[j]表示背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]。
那么如果背包容量为target, dp[target]就是装满背包之后的重量,所以当 dp[target] == target 的时候,背包就装满了。
也还有装不满的时候:拿输入数组 [1, 5, 11, 5],举例, dp[7] 只能等于 6,因为 只能放进 1 和 5。而dp[6] 就可以等于6了,放进1 和 5,那么dp[6] == 6,说明背包装满了。
-
0-1背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。
所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
-
初始化:dp[0] = 0,非0下标也都是0就可以。
从dp[j]的定义来看,首先dp[0]一定是0。
如果题目给的价值都是正整数,那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。
这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了。
-
遍历顺序:先顺序遍历物品(也就是数组从前往后遍历),背包容量需要倒序遍历。
如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!
-
举例推导dp数组
dp[j]的数值一定是小于等于j的。
如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j,理解这一点很重要。
以输入[1,5,11,5] 为例,如图:
最后dp[11] == 11,说明可以将这个数组分割成两个子集,使得两个子集的元素和相等。
class Solution {
public boolean canPartition(int[] nums) {
// dp[j]:容量为j的背包,所背的物品价值最大可以为dp[j]。
int sum = 0;
for (int num : nums) {
sum += num;
}
//总和为奇数,不能平分
if (sum % 2 != 0) return false;
int target = sum / 2;
int[] dp = new int[target + 1];
// 递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
// 初始化:dp[0] = 0,非0下标也可以都是0。
// 遍历顺序:物品(也就是数组从前到后遍历),背包容量需要倒序遍历
for (int i = 0; i < nums.length; i++) {
// 每一个元素一定是不可重复放入,所以从大到小遍历
for (int j = target; j >= nums[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
// 集合中的元素是否正好可以凑成总和的一半
return dp[target] == target;
}
}
这道题目就是一道0-1背包应用类的题目,需要我们拆解题目,然后套入0-1背包的场景。
0-1背包相对于本题,主要要理解,题目中物品是nums[i],重量是nums[i],价值也是nums[i],背包体积是sum/2。