背包问题根据物品数量和取用方式的不同可以分成01背包(物品只有一个),完全背包(物品有无数个),多重背包(物品的个数不相同)和分组背包(按组打包,每组最多选一个)。重点掌握01背包和完全背包就好。有很多问题不会直接以背包的形式来考察,所以实际面对问题可能要经历一个先转换成背包问题的思考过程。下面先对这两个背包用五步法来进行分析。
01背包
1. dp[i][j],表示背包容量为j时,从0到i-1的物品随便取,可以获得的最大价值;
2. 物品只有两种形态,拿或者不拿,所以dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i]);
3. i = 0,j = 0时,可选用物品为0,背包容量为0,dp[i][0] = 0, i = 1 时,当j >= weight[i]时dp[1][j] = value[1];
4. 遍历顺序先i再j或者先j再i都可以,因为dp[i][j]只与左上方的元素有关,不过从理解和底层存储的角度还是先i后j好点;
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();
}
优化空间
压缩为一维矩阵:
dp[j] = max(dp[j], dp[j-weight[i]]+value[i]);
注意遍历顺序依然是双重遍历,但是对于背包容量j要从后往前遍历。
练习题
状态:查看思路Debug后AC。
本题的难点有两处,首先是察觉到sum/2这一个点可以用来进行判断,跟着题目描述的思路进行下去路就走远了;其次就是如何用背包问题的框架对该问题进行适应。
在背包容量为sum/2的条件下,能不能找到组合让dp[sum/2] = sum/2。
代码如下:
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
int len = nums.size();
for(int n : nums){
sum += n;
}
if(sum % 2 == 1) return false;
int target = sum / 2;
vector<int> dp(10001, 0);
for(int i = 0; i < len; ++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;
}
};