01背包裸题
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。
每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
二维数组
-
dp含义:
dp[ i ][ j ] 表示从下标为 [ 0 - i ]的物品里任意取,放进容量为 j 的背包,价值总和最大是多少。 -
递推公式
在分析第 i 个物品 dp[ i ][ j ] 能否放到背包j里时,有以下三种情况
(1)w[ i ] > 背包总重量 不放 dp[ i ][ j ] = dp[ i - 1 ][ j ]
(2)不放 dp[ i ][ j ] = dp[ i - 1 ][ j ]
(3)放进去 dp[ i ][ j ] = dp[ i - 1 ][ j - w[ i ] ] + v[ i ]
则 dp[ i ][ j ] = max (dp[ i - 1 ][ j - w[ i ] ] + v[ i ], dp[ i - 1 ][ j ] -
初始化
如果物品下标从0开始
for (int j = weight[0]; j <= bagweight; j++) { dp[0][j] = value[0];}
如果物品下标从1开始 , 直接初始化为0 就行 -
顺序 由递推公式可见,当前值取决于自己左上角的值,那么先遍历重量,再遍历物品 和 先遍历物品再遍历重量是一样的
一维滚动数组
原理:当前层的状态只与上一层有关系,所以把上一层的数字拷贝下来,到当前层进行计算,用新的值进行覆盖,把二维数组压缩为一维
-
dp含义:
dp[ j ] 容量为j的背包能装的最大价值 -
递推公式
dp[ j ] = max(dp[ j ], dp[ j - w [ i ] ] + v[ i ])
因为该层数据使上一层拷贝下来的所以原本的 dp[ j ] == dp[ i - 1 ][ j ]在分析第 i 个物品 dp[ i ][ j ] 能否放到背包j里时,有以下三种情况
(1)w[ i ] > 背包总重量 不放 dp[ i ][ j ] = dp[ i - 1 ][ j ]
(2)不放 dp[ i ][ j ] = dp[ i - 1 ][ j ]
(3)放进去 dp[ i ][ j ] = dp[ i - 1 ][ j - w[ i ] ] + v[ i ]
则 dp[ i ][ j ] = max (dp[ i - 1 ][ j - w[ i ] ] + v[ i ], dp[ i - 1 ][ j ] -
初始化
dp[ 0 ] = 0 非零下标,由于背包是正数背包,并且更新时求max 那么 初始化为正数的最小值 0 -
顺序 正序遍历物体,逆序遍历背包
列表后面的值需要通过与上一层遍历得到的前面的值比较确定
如果不采用倒序遍历,遍历后一个状态时,要用到前面某个状态的值dp[j-w[i]],而前面的状态值通过正序在本轮遍历已经被覆盖了,而计算后一个状态的值时需要的是覆盖前的值
题目:
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
分析:
那么只要找到集合里能够出现 sum / 2 的子集总和,就算是可以分割成两个相同元素和子集了。
背包容量为 sum / 2 物品为数组nums 重量为 nums[ i ] 价值为 nums[ i ]
dp[sum/2] == sum/2 表示能装满
一维背包 逆序遍历 dp[j] = max(dp[j], dp[j-nums[i]] + nums[i]);
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for(int i=0; i<nums.size(); i++){
sum += nums[i];
}
if(sum%2 == 1) return false;
int c = sum/2;
vector<int>dp(c + 1, 0);
for(int i=0; i<nums.size(); i++){
for(int j=c; j>=nums[i]; j--){
dp[j] = max(dp[j], dp[j-nums[i]] + nums[i]);
}
}
if(dp[c] == c) return true;
return false;
}
};