题目
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例1
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例2
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
思路
思路一
- 问题是数组中是否可以有两个子集,使得子集元素和相等。
- 即找到一个子集,使得元素和等于数组所有元素和的一半。所以当数组所有元素的和为奇数时,必定不可能找到满足要求的子集。
- 找到一个子集,使得元素和等于总和的一半,用half表示。即使用数组中的元素,是否能够填满大小为half的背包。
- 使用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示,前j个数是否能填满大小为j的背包。 i = 0 i = 0 i=0可以假象为大小为0的背包, j = 0 j=0 j=0可以填满为0的背包,所以 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]就等于true。所以当j不等于0时,前j个数都可以填满0的背包。即 d p [ 0 ] [ j ] = t r u e dp[0][j]=true dp[0][j]=true
- 对于
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j],即前j个数是否能填满大小为i的背包,有两种情况。
- 前j-1个数已经填满了大小为i的背包。即 d p [ i ] [ j − 1 ] = t r u e dp[i][j-1] = true dp[i][j−1]=true
- 第j个数设为num,背包容量i大于num,即第j个数占了num的容量,剩下i-num的容量,看前j-1个数是否能填满i-num的容量。即 d p [ i − n u m ] [ j − 1 ] dp[i-num][j-1] dp[i−num][j−1]
代码一
class Solution {
public:
bool canPartition(vector<int>& nums) {
if ( nums.size() < 2 ) return false;
int size = nums.size();
int sum = 0;
for ( auto& n : nums )
sum += n;
if ( ( sum & 1 ) == 1 ) return false;
int half = ( sum >> 1 );
/*二维数组*/
int dp[half+1][size+1];
memset( dp, 0, sizeof(dp) );
dp[0][0] = 1;
for ( int j = 1; j <= size; ++j ) {
for ( int i = 0; i <= half; ++i ) {
dp[i][j] = dp[i][j-1];
if ( i >= nums[j-1] && !dp[i][j] )
dp[i][j] = dp[i-nums[j-1]][j-1];
}
}
return dp[half][size];
}
};
思路二
- 从思路一看出,先关注第一个数是否能填满各个大小的背包,前两个数能否填满…前size个数是否能填满。假如现在考虑前j个数是否能填满i,我们考虑的是前j-1个数是否能填满,或者前j-1个是否能填满i-num,每次更新都用到了前面的值,画图的话就是左上的数据,所以可以用一维的数组存储。
- dp[i]就表示i容量能否被填满。更新的时候从末尾向前更新,这样每次更新不会影响后面的更新。
代码二
class Solution {
public:
bool canPartition(vector<int>& nums) {
if ( nums.size() < 2 ) return false;
int size = nums.size();
int sum = 0;
for ( auto& n : nums )
sum += n;
if ( ( sum & 1 ) == 1 ) return false;
int half = ( sum >> 1 );
int dp[half+1];
memset( dp, 0, sizeof(dp) );
dp[0] = 1;
for ( int j = 1; j <= size; ++j ) {
for ( int i = half; i >= 1; --i ) {
if ( i >= nums[j-1] )
dp[i] = dp[i] || dp[i-nums[j-1]];
}
}
return dp[half];
}
};