动态规划:01背包问题
- 01背包问题基础
- 1. 暴力解法
- 2. 二维dp数组01背包
- 1.确定dp数组以及下标的含义
- 2.递推公式
- 3.dp数组如何初始化
- 4.遍历顺序
- 5.测试代码
- 01背包理论基础(滚动数组):将二维dp转换为一维dp
- 1. dp数组以及下标名义
- 2. 递归公式
- 3. dp数组如何初始化
- 4. 遍历顺序:从后往前遍历
- 5.一维dp01背包完整C++测试代码
- 416. 分割等和子集
- 1. dp数组以及下标名义
- 2. 递归公式
- 3. dp数组如何初始化
- 4. 遍历顺序:从后往前遍历
- 5. 代码
01背包问题基础
1. 暴力解法
每个物品有两种状态,放与不放,可通过回溯暴力求解,复杂度为N(2的n次方)
2. 二维dp数组01背包
1.确定dp数组以及下标的含义
对于背包问题,有一种写法, 是使用二维数组,即dp[i][j] 表示从==,下标为==[0-i]==的物品里任意取,放进容量为j的背包,价值总和最大是多少。
![在这里插入图片描述](https://img-blog.csdnimg.cn/f1dd9f281aca4f8a8c06bee3aece4b64.png
2.递推公式
- 放物品 i : 不放物品 i 时的最大价值加上放物品 i 的价值 :dp[i - 1][j - weight[ i ]] + value[ i ];
- 不放物品i :dp[i - 1][j]推出,当前背包容量为 j ,
- 递推公式:dp[i ][j] = max(dp[i - 1][j],dp[i - 1][j - weight[ i ]] + value[ i ])
3.dp数组如何初始化
初始化第一列,背包容量为0,则价值都为0
初始化第一行,物品为0,当背包为0 时放不了所有价值为0;当背包为1时刚好放下物品,所有价值为15;后序同理为15
非零下标:初始化值不影响
vector<vector<int>>dp(weight.size(), vector<int>(bagweight + 1, 0));
for (int j = 0 ; j < weight[0]; j++) { // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。
dp[0][j] = 0;
}
// 正序遍历
for (int j = weight[0]; j <= bagweight; j++) {
dp[0][j] = value[0];
}
4.遍历顺序
二维dp可以先遍历物品也可先遍历背包
为什么?
因为:递归公式中可以看出dp[i][j]是靠dp[i-1][j]和dp[i - 1][j - weight[i]]推导出来的。dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括正上方向),那么先遍历物品,再遍历背包的过程如图所示:
虽然两个for循环遍历的次序不同,但是dp[i][j]所需要的数据就是左上角,根本不影响dp[i][j]公式的推导!
// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 0; 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]);
}
}
5.测试代码
void test_2_wei_bag_problem1() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagweight = 4;
// 二维数组
vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
// 初始化
for (int j = weight[0]; j <= bagweight; j++) {
dp[0][j] = value[0];
}
// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 0; 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() {
test_2_wei_bag_problem1();
}
01背包理论基础(滚动数组):将二维dp转换为一维dp
1. dp数组以及下标名义
dp[j]:容量为j的背包所能装的最大价值。
2. 递归公式
-
不放物品i :dp[j]可以由dp[j - weight[i]]推出,当前背包容量为 j ,
-
放物品 i : 不放物品 i 时的最大价值加上放物品 i 的价值 :dp[j - weight[ i ]] + value[ i ];
-
递推公式:
max(dp[j], dp[j - weight[ i ]] + value[ i ])入代码片
3. dp数组如何初始化
dp[0] = 0
非零下标:dp数组在推导过程中一定是取价值最大的数,如果题目给的价值都是正整数那么非零下标都初始化为0就可以了,这样才能让dp数组在递推公式的过程中取得最大的价值,而不是被初始值覆盖掉
4. 遍历顺序:从后往前遍历
for(int i = 0; i < weight.size(); i++) {//遍历物品
for(int j = bageweight; j >= weight[i]; j--) {//遍历背包容量
dp[j] = max(dp[j], dp[j - weight[ i ]] + value[ i ]);
}
}
倒序原因,
举例如下表,weight[0] = 1, 价值 value[0] = 15,如果正序遍历
正序
dp[1] = dp[1 - weight[0]] + value[0] = 15
dp[2] = dp[2 - weight[0]] + value[1] = 30 此时物品0 被放了两次
倒序,先计算dp[2]
dp[2] = dp[2 - weight[0]] + value[1] = 15 因为初始化了dp[1]为0
dp[1] = dp[1 - weight[0]] + value[0] = 15
为什么倒序
倒序遍历的原因是,本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖。
为什么二维数组不用倒序
因为对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖!
为什么不可以先遍历背包再遍历物品
因为一维dp的写法,背包容量一定是要倒序遍历,如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。
5.一维dp01背包完整C++测试代码
void test_1_wei_bag_problem() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
// 初始化
vector<int> dp(bagWeight + 1, 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]);
}
}
cout << dp[bagWeight] << endl;
}
int main() {
test_1_wei_bag_problem();
}
416. 分割等和子集
首先,本题要求集合里能否出现总和为 sum / 2 的子集
1. dp数组以及下标名义
dp[j]:容量为j的背包所能装的最大价值。
2. 递归公式
-本题相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。
- 递推公式:
max(dp[j], dp[j - nums[i]] + nums[i])入代码片
3. dp数组如何初始化
dp[0] = 0
非零下标:dp数组在推导过程中一定是取价值最大的数,如果题目给的价值都是正整数那么非零下标都初始化为0就可以了,这样才能让dp数组在递推公式的过程中取得最大的价值,而不是被初始值覆盖掉
/ 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
vector<int> dp(10001, 0);
4. 遍历顺序:从后往前遍历
5. 代码
class Solution {
public:
bool canPartition(vector<int>& nums) {
vector<int>dp(10001 , 0);
int sum = 0;
for(int i = 0; i < nums.size(); i++) {
sum +=nums[i];
}
if(sum % 2 == 1)return false;
for (int i = 0; i < nums.size(); i++) {
for(int j = sum/2; j >= nums[i]; j--) {
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
if(dp[sum/2] == sum / 2)return true;
return false;
}
};