代码随想录算法训练营
---day35
文章目录
- 代码随想录算法训练营
- 前言
- [一、01背包问题 二维](#一、01背包问题 二维)
- [二、 01背包问题 一维 (滚动数组)](#二、 01背包问题 一维 (滚动数组))
- [三、416. 分割等和子集](#三、416. 分割等和子集)
- 总结
前言
今天是算法营的第35天,希望自己能够坚持下来!
今天开始是背包问题了,今日任务:
● 01背包问题 二维
● 01背包问题 一维
● 416. 分割等和子集
先上个图,背包问题的分类:
一、01背包问题 二维
01背包问题:
有n个物品和一个最多能装重量为w的背包,每个物品的价值和重量不同,问将哪些物品装入背包里物品价值总和最大。
因为有物品 和 背包容量两个维度,所以dp数组需要用二维,i 来表示物品、j表示背包容量。
思路:
-
dp[i][j]的定义为:表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
-
递归公式:对于物品i,在容量为j的时候都有放和不放物品i两种情况,那么dp[i][j]就是取放和不放两种情况的最大值(价值总和最大)
·不放:就是只考虑i-1之前的物品dp[i-1][j]
·放:则是先腾出物品i的容量,再加上物品i的价值dp[i-1][j - weight[i]] + value[i]
那么dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
-
初始化:
·首先,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。
·然后根据递推公式可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。
当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。
-
遍历顺序:先遍历物品还是先遍历背包重量都可以,但先遍历物品更好理解。
-
举例推导dp数组:
代码如下:
cpp
#include<iostream>
#include<vector>
using namespace std;
int main() {
int n, bagweight;
cin >> n >> bagweight;
vector<int> weight(n, 0); //每种研究材料的重量
vector<int> value(n, 0); //每种研究材料的价值
for (int i = 0; i < n; i++) {
cin >> weight[i];
}
for (int j = 0; j < n; j++) {
cin >> value[j];
}
//i是研究材料,j是行李箱空间
//dp[i][j]含义:在j的空间里,任意放入i和i一下的研究材料所得到的最大价值
//对于研究材料,只有放入和不放入两种状态
//所以对于d[i][j]来说,就是取放入材料i和不放入材料i,这两种情况取价值最大的
//不放材料i: d[i-1][j], 放材料i:d[i-1][j-weight[i]] + value[i]
vector<vector<int>>dp (weight.size(), vector<int>(bagweight+1, 0));
//初始化dp,遍历背包空间,记录,也对能装下材料0的j对应的dp[0]初始化
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++) {
//如果当前j大小的空间装不下物品i的话,那么继承dp[i-1][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[" << i << "][" << j << "]:" << dp[i][j] << endl;
}
}
cout << dp[n-1][bagweight] << endl;
return 0;
}
关于为什么可以调换遍历顺序:
先遍历物品,再遍历背包的过程如图所示:
先遍历背包,再遍历物品呢,如图:
不管是先遍历物品还是先遍历背包,dp[i][j]所需要的数据就是左上角,都是可以推导出来的。
二、 01背包问题 一维 (滚动数组)
在使用二维数组的时候,递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
而滚动数组原理其实就是将dp[i-1]层的信息拷贝到dp[i]层来,也就是只用一层来记录信息,然后就可以把dp[i]维度去掉,只剩下
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
思路:
- dp[j]的定义为:容量为j的背包,所背的物品价值可以最大为dp[j]。。
- 递归公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
- 初始化:因为递推的时候是求的最大值,且只用到了j, 所以初始化成0就可以了。
- 遍历顺序:一维dp数组需要先物品,再遍历背包,且背包是倒序遍历的,因为正序遍历的话物品会重复放入。(如果是完全背包的话就是正序遍历)
- 举例推导dp数组:
代码如下:
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, bagweight;
cin >> n >> bagweight;
vector<int>weight (n);
vector<int>value (n);
for (int i = 0; i < n; i++) {
cin >> weight[i];
}
for (int i = 0; i < n; i++) {
cin >> value[i];
}
//定义给定行李空间的一维数组
vector<int>dp (bagweight+1, 0);
//遍历物品
for (int i = 0; i < n; i++) {
//后序遍历空间,只遍历能装下当前物品i的空间
for (int j = bagweight; j >= weight[i]; j--) {
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[bagweight] << endl;
return 0;
}
三、416. 分割等和子集
思路:
问题可以转化成,找到一个子集等于数组总和sum的一半。
也就是背包容量是sum/2,找出是否有加起来是sum/2的物品。
本题我用一维dp数组解题。
- dp[j]的定义为:容量为j,子集加起来总和为dp[j],也就是最后要求dp[target] == target
- 递归公式:01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
本题物品的重量就是价值,所以dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]); - 初始化:从dp[j]的定义来看,首先dp[0]一定是0。而题目说了价值都是正整数,那么其他也初始化成0就可以了。如果价值有负数出现,那么需要初始化成负无穷。这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了。
- 遍历顺序:一维dp数组的话,需要先遍历物品,再遍历背包,且背包需要后序遍历。
- 举例推导dp数组
代码如下:
cpp
class Solution {
public:
//动态规划,转化成背包问题
//只需要要求集合的总和sum/2,找出是否有和为sum/2的子集
//相当于用sum/2的背包去装sum/2的东西
//一维背包问题递推公式:dp[j] = max(dp[j], dp[j-weight[i]] + value[i])
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int num:nums) {
sum += num;
}
if (sum % 2 == 1) return false;
int target = sum / 2;
vector<int> dp (target + 1, 0);
for (int i = 0; i < nums.size(); i++) {
for (int j = target; j >= nums[i]; j--) {
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
if (dp[target] == target) return true;
return false;
}
};
总结
01背包:n个物品,w空间的背包,每个物品重量不同,价值不同,求背包装最多的价值是多少。
二维dp数组:
- dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + vlaue[i])
- 需要初始化最上层和最左层
- 先遍历背包还是物品都可以,因为两种方式都可以得到左上角的数据,先遍历物品比较好理解。
一维dp数组:
- dp[j] = max(dp[j], dp[j- weight[i]] + value[i]); 就是二维中去掉i维度,理解成将i-1层的数据拷贝到i层
- 因为不涉及到i,所以初始化成0就可以了(如果价值有负数,除了dp[0],需要初始化成负无穷)
- 只能先遍历物品再遍历背包,且背包需要后续遍历(正序的话会重复放入物品,01背包每个物品只能用一次)
明天继续加油!