题目如下
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
解题思路
动态规划
这道题可以换一种表述:给定一个只包含正整数的非空数组 nums[0],判断是否可以从数组中选出一些数字,使得这些数字的和等于整个数组的元素和的一半。因此这个问题可以转换成「0−1 背包问题」。这道题与传统的「0−1 背包问题」的区别在于,传统的「0−1 背包问题」要求选取的物品的重量之和不能超过背包的总容量,这道题则要求选取的数字的和恰好等于整个数组的元素和的一半。类似于传统的「0−1 背包问题」,可以使用动态规划求解。
在使用动态规划求解之前,首先需要进行以下判断。
创建二维数组dp,包含 n 行 target+1 列,其中 dp[i][j] 表示从数组的[0,i] 下标范围内选取若干个正整数(可以是 0 个),是否存在一种选取方案使得被选取的正整数的和等于 j。初始时,dp 中的全部元素都是 false。
在定义状态之后,需要考虑边界情况。以下两种情况都属于边界情况。
对于i>0 且 j>0 的情况,如何确定 dp[i][j] 的值?需要分别考虑以下两种情况。
最终得到 dp[n−1][target] 即为答案。
class Solution
{
public:
bool canPartition(vector<int>& nums)
{
int n = nums.size();
if (n < 2)
{
return false;
}
int sum = accumulate(nums.begin(), nums.end(), 0);
int maxNum = *max_element(nums.begin(), nums.end());
if (sum & 1)
{
return false;
}
int target = sum / 2;
if (maxNum > target)
{
return false;
}
vector<vector<int>> dp(n, vector<int>(target + 1, 0));
for (int i = 0; i < n; i++)
{
dp[i][0] = true;
}
dp[0][nums[0]] = true;
for (int i = 1; i < n; i++)
{
int num = nums[i];
for (int j = 1; j <= target; j++)
{
if (j >= num)
{
dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
}
else
{
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n - 1][target];
}
};
class Solution
{
public:
bool canPartition(vector<int>& nums)
{
int n = nums.size();
if (n < 2)
{
return false;
}
int sum = 0, maxNum = 0;
for (auto& num : nums)
{
sum += num;
maxNum = max(maxNum, num);
}
if (sum & 1)
{
return false;
}
int target = sum / 2;
if (maxNum > target)
{
return false;
}
vector<int> dp(target + 1, 0);
dp[0] = true;
for (int i = 0; i < n; i++)
{
int num = nums[i];
for (int j = target; j >= num; --j)
{
dp[j] |= dp[j - num];
}
}
return dp[target];
}
};