leetcode 416:分割等和子集
416. 分割等和子集
给你一个 只包含正整数 的 非空 数组 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
Related Topics
数组
动态规划
思路1:回溯(超时)
分析:找到2个相等的子集,如果这个子集总和是奇数,那么永远不会存在2个相等的子集。
在偶数的情况可能存在,我们只需要找到一个和为sum/2的子集就可以。
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for(int num : nums){
sum += num;
}
if( sum % 2 != 0){
return false;
}
int target = sum/2;
return dfs(nums,0,target);
}
public boolean dfs(int[] nums,int index,int target){
if(target == 0){
return true;
}
if(index == nums.length || target < 0){
return false;
}
return dfs(nums,index+1,target-nums[index]) || dfs(nums,index+1,target);
}
}
思路2:动态规划
dp表示前i个数能否构成和为j的子集。
分析:
- 如果数组长度为1,无法构成子集。
- 如果数组和为奇数,也无法构成2个相等的子集。
- 我们需要找到数组中一个子集和(target)为总和的一半的子集。
- 如果数组中的最大值大于target,说明剩下的元素和小于target,无法构成子集。
接下来是动态规划:
- 首先,任意范围内的数,都可以构成空集,也就是和为0。所以需要设置边界为true。
- 遍历每一个数,
- 如果这个数大于j,说明这个数肯定不能构成子集,只需要判断前面i-1个数是否能构成。
- 这个数小于j,说明这个数可以构成子集
dp[i][j-num]
,也可能不需要,前面i-1个数已经构成了dp[i-1][j]
class Solution {
public boolean canPartition(int[] nums) {
//如果长度为1 不可能分为2个子集
int len = nums.length;
if(len < 2){
return false;
}
//求和
int sum = 0;
int max = Integer.MIN_VALUE;
for(int num : nums){
sum += num;
max = Math.max(max,num);
}
//奇数和无法构成
if( sum % 2 != 0){
return false;
}
//寻找目标子集和
int target = sum/2;
//如果target小于max,那么剩下的元素和肯定小于target 也无法构成
if(target < max){
return false;
}
//动态规划 判断数组是否能找子集和为target dp表示前i个数范围内能否构成目标为j的数
boolean[][] dp = new boolean[len+1][target+1];
//前i个中任意一个组合都可以构成目标为0的集合 也就是空集
for(int i = 0 ; i < len+1; i++){
dp[i][0] = true;
}
for(int i = 1; i < len + 1;i++){
int num = nums[i-1];
for(int j = 1 ; j <= target;j++){
if(num > j){
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = dp[i-1][j] || dp[i-1][j-num];
}
}
}
return dp[len-1][target];
}
}