1.分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
- 每个数组中的元素不会超过 100
- 数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
题解:分割等和子集,那就先求和,再平分。如果不能平分,那就直接返回false,否则,问题就变成了判断数组是否存在若干个元素求和正好等于数组和的一半,那就直接遍历所有的结果,深搜解决,可以,这种方式很容易想到。还有一种方法是将这个问题转化为背包问题<背包问题详解传送门>,使背包的大小为数组和的一半,问题转化为是否存在数组元素使背包正好被装满,也就是最优解是数组和的一半。
深搜解法:
class Solution {
public boolean canPartition(int[] nums) {
int sum=0;
for(int n:nums)
sum+=n;
if(sum%2!=0)
return false;
return subsetSum(nums,sum>>>1);
}
public boolean subsetSum(int[] nums, int s) {
boolean[] dp = new boolean[s + 1];
dp[0] = true;
for (int n : nums){
for (int i = s; i >= n; i--){
dp[i] =dp[i]||dp[i - n];
}
if(dp[s])
return true;
}
return dp[s];
}
}
背包解法:
class Solution {
public boolean canPartition(int[] nums) {
int n=nums.length;
int sum=0;
for(int i=0;i<n;i++){
sum+=nums[i];
}
int t=sum/2;
if(sum%2!=0)
return false;
int[] dp=new int[t+1];
for(int i=0;i<n;i++){
for(int j=t;j>=nums[i];j--){
dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
if(dp[t]==t)
return true;
return false;
}
}
2.划分为k个相等的子集
给定一个整数数组 nums
和一个正整数 k
,找出是否有可能把这个数组分成 k
个非空子集,其总和都相等。
示例 1:
输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出: True
说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。
注意:
1 <= k <= len(nums) <= 16
0 < nums[i] < 10000
题解:本题与上一题解法基本相同,只不过是把平分改为多分而已。
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
int n=nums.length;
int sum=0;
for(int i=0;i<n;i++)
sum+=nums[i];
boolean[] v=new boolean[n];
if(sum%k!=0)
return false;
return dfs(nums,k,sum/k,0,0,v);
}
public static boolean dfs(int[] nums,int k,int target,int sum,int s,boolean[] v){
if(k==1)
return true;
if(target<0)
return false;
if(target==sum)
return dfs(nums,k-1,target,0,0,v);
for(int i=s;i<nums.length;i++){
if(v[i])
continue;
v[i]=true;
if(dfs(nums,k,target,sum+nums[i],i+1,v))
return true;
v[i]=false;
}
return false;
}
}