题目描述:
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
给你一个 只包含正整数 的 非空 数组 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
c++代码:
class Solution {
public:
bool canPartition(vector<int>& nums) {
int n = nums.size();
if(n<2)return false;//若数组元素少于2,不可能分割出两个数组
int sum = 0,max=nums[0];
for(int i=0;i<n;i++){
sum+=nums[i];
if(nums[i]>max)max=nums[i];
}
if(sum%2!=0 || max>(sum/2))return false; //若数组元素总和不能平分或最大值超过一半,不可能分割出两个数组
int dp[n][sum/2+1]; //dp[i][j]记录从第0个到到第i个数的总和为j的可能性,1位可能,0位不可能
for(int i=0;i<n;i++){
for(int j=0;j<=sum/2;j++)dp[i][j]=0;
}
for(int i=0;i<n;i++)dp[i][0]=1; //一个数都不要就可以让和等于0
dp[0][nums[0]]=1; //初始化第一个可以直观计算的dp[i][j],dp[0][nums[0]]=1
for(int i=1;i<n;i++){
for(int j=1;j<=(sum/2);j++){
if(nums[i]<=j){ //当前nums[i]小于目标和j,可以要nums[i],也可不要nums[i]
dp[i][j]=dp[i-1][j-nums[i]] | dp[i-1][j];
}
else if(j<nums[i]){ //nums[i]大于目标和,不能要nums[i]
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n-1][sum/2];
}
};
问题为“使得两个数组加和结果相同”,这可以转化为在一个数组中找到几个数字,使其加和结果为数组总和的一半。若数组总和为奇数,或者最大的数字比总和的一半还大,那么不可能分割出两个加和结果相同的数组。
问题可以分解成每个子问题,每个子问题直接又相互不独立。使用动态规划。
dp[i][j]表示从第0个到第i个数字,之和等于j的可能性,0表示不可能,1表示可能。
对于每个数字nums[i],考虑nums[i]和j的大小关系,如果nums[i]下于目标和j,则一定不能选择nums[i],加和结果也没变,则关系式为dp[i][j] = dp[i-1][j];如果nums[i]大于等于目标和j,那么可以选择nums[i],也可以不选择nums[i],这两种可能只要有一种可以满足目标和,就说明到第i个位置j是可以满足的,则关系式为dp[i][j] = dp[i-1][j] | dp[i-1][j-nums[i]]
注意初始化,当j=0,只要不选择任何数字,则一定可以满足加和等于0,所以所有的dp[i][0]=0。此外,再初始化一下j=0时的取值,可以肯定的是dp[0][nums[0]]=1。
两层for循环来计算所有的dp[i][j],最后dp[n-1][sum/2]即为问题的答案。
总结:
问题转化,“两个相等”类型的问题可以转化为“总和的一半”,转化为常规的动态规划问题。用dp[i][j]来逐步计算。注意初始化dp(i=0的时候,j=0的时候,dp[i][j]的值为初始值)。