1.确定dp数组以及下标的含义
dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
gpt 解决我的困惑
另外:
当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。
当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。
所以初始化总共有两部分(第一列,第一行)
其他格子值无所谓,反正每个格子是通过左上得出来
4. 遍历顺序 也很重要。会是有的题目的难点。
本题两层for loop 物品套背包容量或者背包容量套物品都可以,因为都是往右下走就行了
随想录写的是一个从0开始一个从1开始,但是我觉得 i j 都从1开始就行
5. 举例推导dp数组
在格子里自己推导一遍看看对不对
测试代码(用随想录的按自己偏好修改了一点)
void test_2d_bag_problem1(vector &weight, vector &value, int &bagweight) {
// 二维数组+初始化1
vector> dp(weight.size(), vector(bagweight + 1, 0));
// 初始化2
for (int j = weight[0]; j <= bagweight; j++) {
dp[0][j] = value[0];
}
for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 1; j <= bagweight; j++) { // 遍历背包容量
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
cout << dp[weight.size() - 1][bagweight] << endl;
}
int main() {
vector weight = {1, 3, 4};
vector value = {15, 20, 30};
int bagweight = 4;
test_2d_bag_problem1(weight,value,bagweight);
}
# 一维滚动dp数组,01背包
依然是两层for loop, ij都要有,但是只有一层数组了
! 一维数组遍历顺序只能物品(外层)套背包容量,因为我们必须一行行算出结果后下一行继续用
! 一维数组遍历顺序 背包容量 只能 倒序,因为要保证每个物品只用一次,具体理解:
二维每次更新要看斜上方一个值(还是原来的没被覆盖),一维每次更新对应的只能看同行前面某个值,但是如果从前往后已经被这一行的一种新的情况覆盖了。但是如果从后往前的话, 用的dp[j - weight[i]]还是之前那行的
void test_1d_bag_problem1(vector &weight, vector &value, int &bagweight) {
// 二维数组+初始化1
vector dp(bagweight + 1, 0);
// 初始化2
for (int j = weight[0]; j <= bagweight; j++) {
dp[j] = value[0];
}
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagweight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
//for(int j = 1; j <= bagweight; j++) { //if (j >= weight[i])
//原来是这样的,但是现在因为倒过来,不需要了,只用决定终止的点是weight[i]就行
cout << dp[bagweight] << endl;
}
int main() {
vector weight = {1, 3, 4};
vector value = {15, 20, 30};
int bagweight = 4;
test_1d_bag_problem1(weight,value,bagweight);
}
面试的问法:
#416. 分割等和子集
自己没想出来(快想出来了,j的值已经确定了是从0,1,逐渐到sum)
随想录思路:
1. 需要一个subset和是sum/2 (我居然没想出来这个),我一直在想两个subset之和相减为0
背包的体积为sum / 2,j 就是 从 0,1 逐渐到sum
2. 背包中每一个元素是不可重复放入。(01背包)
3. 关键:"每一个元素的数值既是重量,也是价值” 作为重量的话,是用来限制不能超过sum/2 ,做为价值是我们希望他的和尽可能大,以至于到达sum/2。
4. 关键:背包如果正好装满,说明找到了总和为 sum / 2 的子集。
不能装满的情况也是有的:比如 1 5 11 5 ,这个list ,当 j =7 时(因为我们要一直连续过去,所以才会出现这个值)最多1+5只能6,所以就是不能装满
求和也可以用库函数:int sum = accumulate(nums.begin(), nums.end(), 0);
bool canPartition(vector& nums) {
int sum = 0;
for (int &ele:nums) sum+=ele;
// 也可以使用库函数一步求和
// int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum % 2 == 1) return false;
int target = sum / 2;
vector dp(target+1, 0);
for(int j=nums[0];j<=target;j++) dp[j]=nums[0];
// 开始 一维01背包
for(int i = 1; i < nums.size(); i++) {
for(int j = target; j >= nums[i]; j--) {
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
// 集合中的元素正好可以凑成总和target
if (dp[target] == target) return true;
return false;
}
觉得自己文章摘要总结的很好:
二维01背包:dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
一维01背包:只能物品外层,j=bagweight;j>=weight[i];j--
416. 分割等和子集 :正好装满sum/2, nums[i] 同时是weight和value