给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
- 每个数组中的元素不会超过 100
- 数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5] 输出: true 解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5] 输出: false 解释: 数组不能分割成两个元素和相等的子集.
思路:
这道题其实可以变成是0 1 背包问题,申请二维数组dp[i][j](0<=i<=nums.size(),0<=j<=(sum/2),sum是数组的和的一半),要划分成两半且和相等,即sum(left)=sum(right),那么原数组和必须是偶数,否则无法划分。其中dp[i][j]表示从第一个元素到第i个元素是否存在能组成和为j的子集,如果可以为true,否则为false。
接下来我们来看递推公式,看看dp[i][j]可以怎么由子问题推导而来,先给出公式:
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
1:如果不考虑第i个元素,那么情况等于前i-1个元素的情况即dp[i-1][j](前i-1个元素如果已经可以划分子集左,那么剩下的元素直接划分到另外一边即子集右即可)
2:如果考虑第i个元素,那么情况等于前i-1个元素的子集和加上第i个元素的和可以组成和j,j-nums[i]表示前i-1个元素可以组成和为j-nums[i],那么加上第i个元素nums[i],和即可j,可以组成子集。
所以是这两种情况的或构成递推公式,我一直觉得这道题和背包问题的理解有点出入,因为01背包问题是背或者不背,但一定所有的元素最后都会有结果(背或者不背),而这道题元素的背指的在左半边子集,不背指的在右半边子集,我们的目标是使得左半边子集的和等于总和的一半。这样思考才会和背包问题对应上。
二维数组版:
class Solution {
public:
bool canPartition(vector<int>& nums) {
bool res = false;
int sum = 0;
int m = nums.size();
for (int i = 0; i < m; i++) {
sum += nums[i];
}
if ((sum & 1) == 1) {
return false;
}
sum /= 2;
bool **dp = new bool *[nums.size() + 1];
for (int i = 0; i <= m; i++) {
dp[i] = new bool[sum + 1];
}
for (int i = 0; i <= m; i++) {
dp[i][0] = true;
}
for (int i = 1; i <= sum; i++) {
dp[0][i] = false;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= sum; j++) {
if (j >= nums[i - 1]) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
}
else {
dp[i][j] = dp[i - 1][j];
}
}
}
res = dp[m][sum];
for (int i = 0; i <= m; i++) {
delete[] dp[i];
}
delete[] dp;
return res;
}
};
我们把空间复杂度优化到一维,观察到对于内循环:
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= sum; j++) {
if (j >= nums[i - 1]) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
}
else {
dp[i][j] = dp[i - 1][j];
}
}
}
我们其实只需要上一层i-1的状态,所以我们把二维数组压缩为一维,递推公式如下:
dp[j] =dp[j] || dp[j - nums[i - 1]]; j>=nums[i-1]
这里还需要注意一点,内层循环需要从大到小,避免被覆盖。
参考代码:
class Solution {
public:
bool canPartition(vector<int>& nums) {
bool res = false;
int sum = 0;
int m = nums.size();
for (int i = 0; i < m; i++) {
sum += nums[i];
}
if ((sum & 1) == 1) {
return false;
}
sum /= 2;
bool *dp = new bool [sum + 1];
for (int i = 1; i <= sum; i++) {
dp[i] =false;
}
dp[0] = true;
for (int i = 0; i < m; i++) {
for (int j = sum; j > 0; j--) {
if (j >= nums[i]) {
dp[j] =dp[j] || dp[j - nums[i]];
}
}
}
res = dp[sum];
delete[] dp;
return res;
}
};