给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
- 每个数组中的元素不会超过 100
- 数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5] 输出: true 解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5] 输出: false 解释: 数组不能分割成两个元素和相等的子集.
解题思路:
dp解决0/1背包
这个问题基本上是让我们找出集合中是否有几个能够求和到特定值的数字(在这个问题中,值是sum / 2)。
实际上,这是一个0/1背包问题,对于每个数字,我们可以选择与否。我们假设dp [i] [j]表示是否可以从前i个数中得到特定的和j。如果我们可以从0-i中选择这样一系列的数字,其和为j,则dp [i] [j]为真,否则为假。
基本情况:dp [0] [0]为真; (零数由0和0组成)
转换函数:对于每个数字,如果我们不选择它,dp [i] [j] = dp [i-1] [j],这意味着如果第一个i-1元素已经使它成为j,dp [我] [j]也会把它变成j(我们可以忽略nums [i])。如果我们选择nums [i]。dp [i] [j] = dp [i-1] [j-nums [i]],表示j由当前值nums [i]组成,剩余部分由其他先前的数字组成。因此,转移函数是dp [i] [j] = dp [i-1] [j] || DP [I-1] [j-NUMS [I]]
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum % 2 != 0)return false;
int half = sum / 2;
int n = nums.size();
vector<bool>dp(half + 1, false);
dp[0] = true;
for (auto num : nums) {
for (int i = half; i>0; i--) {
if (i >= num) {
dp[i] = dp[i] || dp[i - num];
}
}
}
return dp[half];
}
};
优化
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sums = accumulate(nums.begin(),nums.end(),0);
if((sums&1)==true){
// 奇数
return false;
}
vector<int>dp(sums+1);
dp[0]=1;
for(auto num:nums){
for(int i=sums>>1;i>=0;i--){
if(dp[i]) dp[i+num]=1;
}
if(dp[sums>>1]) return true;
}
return false;
}
};