题目描述:
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
分析:
我能想到前一半的思路。即,先计算整个数组的和sum,如果sum为奇数,那么必不可能分为两个相等的子集(例如,数组和为11,则两个子集和为5.5,而数组中全是整数,因此不可能)。如果数组中最大元素>sum/2,则也不可能分为两个相等子集。如果以上情况都不存在,那么,该问题就转化为了,一个数组中是否存在和为sun/2的子集。
我想到的办法是回溯,但回溯的时间复杂度太大了,就没有去真的写。去看了看题解,背包问题的转化版,即面对一个数字,有选择或者不选择该数字两种方法,他们能够组成的所有情况,通过数组dp进行递推。
bool数组dp[i][j]代表,从0~i个元素中选取若干个,他们的和能否为j。
初始情况下,dp全为false。然后考虑nums[0],只有一个元素nums[0]的情况下,dp[0][0]代表,从第0个元素中,选取若干个,和能否为0,当然可以,不选nums[0],那么和就为0;因此dp[0][0]=true。还有一种情况,那就是选取nums[0],在这种情况下,dp[0][nums[0]]为true,即选取第0个元素,和为nums[0]。
递推公式显而易见了:
dp[i][j]=dp[i-1][j] || dp[i-1][j-nums[i]]
即如果前i-1个元素的和已经为j,那么前i个元素的和,也必定能够为j。如果之前i-1个元素的和为j-nums[i],那么选取当前元素,加上nums[i],也刚好能够构成和为j。除此之外,其他情况为false。
详细分析请见官方题解:
代码如下:
class Solution {
public:
bool canPartition(vector<int>& nums) {
// 计算nums长度
int len=nums.size();
if(len<1) return false;
int sum=0;
for(int i=0;i<len;i++)
sum+=nums[i];
// 和为奇数,返回false
if(sum%2==1) return false;
sort(nums.begin(),nums.end());
// 最大元素>sum/2,返回false
if(nums[len-1]>sum/2) return false;
// dp[i][j]代表0~i个元素的和是否为j
// 205和sum+5是为了防止数组越界
// 两种方式声明二维数组并赋初始值
bool dp[205][sum+5];
memset(dp,false,sizeof(dp));
// vector<vector<bool>> dp(205,vector<bool>(sum+5,false));
// 初始化面对第0个元素时的情况
dp[0][0]=true;
dp[0][nums[0]]=true;
// 通过递推方程求解之后的dp
for(int i=1;i<len;i++)
{
for(int j=0;j<=sum/2;j++)
{
// 之前的和已经为j,那么不选取该元素
if(i-1>=0&&dp[i-1][j])
dp[i][j]=true;
// 之前的和为j-nums[i],那么选取该元素
else if(i-1>=0&&j-nums[i]>=0&&dp[i-1][j-nums[i]])
dp[i][j]=true;
// 其他情况均为false
else dp[i][j]=false;
}
}
return dp[len-1][sum/2];
}
};
tips:
1.声明数组时尽量初始化
本来使用bool dp[205][sum+5]这种方式声明二维数组,但是这种声明没有办法在声明数组的时候给数组内所有元素赋初始值,必须通过for循环赋值。
我并没有给每个元素都赋了初始值,在运行时报错了:
runtime error: load of value 128, which is not a valid val
去网上查说是没有初始化的问题,于是只好放弃这种做法,改为使用vector来声明二维bool数组。
2.声明数组并初始化的方法
1.使用memset
bool flag[200][50];
memset(flag,false,sizeof(flag))
2.使用vector
vector<vector<bool>> flag(200,vector<bool> (50,false));
3.vector声明数组并赋初始值的几种方式
1.vector<int> a;
声明一维数组a,长度为0,没有初始值
2.vector<int> a(5)
声明一维数组a,长度为5,没有初始值
3.vector<int> a(5,10)
声明一维数组a,长度为5,初始值均为10
4.vector<vector<int>> a(20,vector<int>(10,100))
声明二维数组a,行为20,列为10,初始值均为100