01背包
01,指的就是只能放进去一次。
就是每次打算放一个东西时,首先要考虑它放不放得下,放不下的话就直接不放;放得下的话,就要看放他划算还是不放它划算。
记住几个点: 1。每一个单元格都是当前背包容量下的最优解 2。对于一个背包先看他能不能放下当前的物体,不能放下就不放(但是得放之前求得的当前背包容量下的最优解(总不可能荣包空着吧),这一格的头上那一个) 3。如果当前面背包可以放下当前物品,就把这个东西放进去,但是只放这个东西的话包可能还有空间。就把包的容量减去当前物品的重量,得到包还剩下多少空间,然后去上一行查这个空间可以放多少东西。(一个物体可能会有负价值,为了放在放进去后占了包的空间却减少了总价值,还需要判断一个这个东西放进去和不放进去谁的价更高,因此才会推出递推公式:
cpp
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]);
}
}
其他部分可以画一个二维数组理解它的初始化:左上角是满的,就可以让递推这个过程从左上角任意一个最划算的元素转移过来。
因此一维数组的j就是递减的了:
cpp
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]);
}
}
分割等和子集
不简单。光看题目描述和title,还以为是回溯题呢。。
cpp
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
// dp[i]中的i表示背包内总和
// 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
// 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
vector<int> dp(10001, 0);
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
}
// 也可以使用库函数一步求和
// int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum % 2 == 1) return false;
int target = sum / 2;
// 开始 01背包
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]);
}
}
// 集合中的元素正好可以凑成总和target
if (dp[target] == target) return true;
return false;
}
};