题干:
代码:
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for(int i : nums){
sum += i;
}
if(sum % 2 != 0)return false;
int target = sum / 2;
vector<int>dp(10001, 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背包,写法还是不一样的。
要明确本题中我们要使用的是01背包,因为元素我们只能用一次。
回归主题:首先,本题要求集合里能否出现总和为 sum / 2 的子集。
只有确定了如下四点,才能把01背包问题套到本题上来。
- 背包的容量为sum / 2
- 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
- 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
- 背包中每一个元素是不可重复放入。
定义dp[j]:容量为j的背包所能装下的最大价值,本题则是将容量为sum / 2的背包装满。而且本题物品的大小与价值相等,等同于num[i]。
递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i](weight[i] == value[i] == nums[i])
初始化:
从dp[j]的定义来看,首先dp[0]一定是0。
如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。
这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了。
本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。
代码如下:
// 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
// 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
vector<int> dp(10001, 0);
遍历顺序:一定是先物品再背包,且背包从大到小。i初始是0, < nums.size() 遍历下标不用说;j初始是sum / 2,j >= nums[i](能放选择不放和压根放不了是两码事)。