思路:动态规划
g给定的数组为nums
首先,长度小于2的数组,直接返回false,因为不可分为两个子集。
然后我们要用一个循环来计算给定数组的总和sum。如果sum为奇数也直接返回false。
然后将target = sum/2。
官方题解:
建立一个元素为boolean的二维数组res,res的行数为nums.length ,列数为target+1。其中res [ i ] [ j ] 表示,从nums[0] 到nums [ i ] 的所有元素中,是否存在和为 j 的子集?
例如 nums = {1, 5, 11 , 5}
我们可以求出来target = 12 。
首先将表列出来:
当j=0的时候,意思是是否存在一个子集使总和为0,显然任何集合都有一个子集叫空集。所以最左边那一列全部置为true。
当i = 0 的时候,意思是当前集合中只有一个元素nums[ i ],只有当 j 也等于 nums [ i ] 的时候,当前集合中才有元素的和等于 j。所以将 res[ 0 ] [ nums[0] ] 也置为true。注意nums [0] 也可能大于target。现在初始化完成, i>0 j>0。如下所示:
当我们考虑 res[ 1 ] [ 1 ] 这个位置改填啥的时候,意思就是 当前集合为 {1,5} , 我们要判断是否能找出几个元素组成的子集,使他们的和为 1。我们首先比较一下当前遍历到的nums [ i ] = 5,发现5 是大于当前的 j = 1的。这就说明我们如果要能选出一个子集,使总和为1,那也只能在当前集合去掉nums [ i ] =5 的子集合{nums[0] ~nums [ i-1] }中找了。对了,判断在集合{nums[0] ~nums [ i-1] }能否找到一个子集,使其等于j ,这个我们之前不是判断过了吗?而且就保存在res[ i-1] [ j ]里面啊,所以当nums[ i ] > j 时,我们直接令res[ i ] [ j ] = res[ i -1] [ j ] 。这样第一列就填完了。就是下面这个样子:
然后到了填第二列的时候了。和之前一样,nums[ i ] 全部大于2,直接全部置为false,如下所示:
第3,4列全是这样,就按之前的来,res[ i ] [ j ] = res[ i -1] [ j ] ,全部为false。
当j =5的时候,情况变得有点不一样了。先看res( 1,5) 这个位置改填啥?nums[ i ] <= j 的时候,分为两种情况。
我们先假设,最终我们要求的那个和为 j 的子集里面,是有nums [ i ] 的。我们先用j- nums [ i ] ,然后我们再去找,去掉nums[ i ] 的子集里面,是否存在某个子集的总和 = j- nums [ i ] 。
其实就是在找res[ i-1 ][ j - nums [ i ] ],这个值我们之前也求过了,就是res [0 ][ 0] =true。
但是还有可能,和为 j 的子集里面,不包含nums [ i ] ,这个时候我们就去找res[ i-1 ][ j ],正好它之前也求过。
所以当nums[ i ] <= j 的时候,需要考虑两种情况,这两种情况有一种为true,就说明假设成立。所以,res[ i ][ j ] = res[ i-1 ][ j - nums [ i ]] or res[ i-1 ][ j ]。
所以可以写出递推公式:
最后,我们选取那个右下角的元素输出。
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
if(nums.length<2)
return false;
for(int i=0;i<nums.length;i++)
{
sum += nums[i];
}
if(sum%2!=0)
return false;
sum = sum/2;
boolean[][] res = new boolean[nums.length][sum+1];
for(int i=0;i<nums.length;i++)
{
res[i][0] = true;
}
for(int j=0;j<sum+1;j++)
{
if(j==nums[0])
res[0][j] = true;
}
for(int i=1;i<nums.length;i++)
{
for(int j=1;j<sum+1;j++)
{
if(nums[i]<=j)
{
res[i][j] = res[i-1][j]||res[i-1][j-nums[i]];
}
else
{
res[i][j] = res[i-1][j];
}
}
}
return res[nums.length-1][sum];
}
}
第二种思路:
也是求出target , 然后设立一个集合来保存当前已有的所有组合的和set。 首先把0加进set里面。
然后遍历nums。
每当遍历到一个元素的时候,我们都用target -nums[ i ] ,然后取set里面找是否有target -nums[ i ]。如果有那么就直接返回true。
如果没有,那么就建立一个新的集合,将set里的所有元素都加上nums[ i ] ,在全部放回set中。
具体过程如下:
{1,5, 11, 5}
遍历到1 set {0}
找set里面是否存在11, 不存在,那么就将 {1} 加入set中,set变为{0,1}
遍历到5 set {0,1}
找set里面是否存在7, 不存在,那么就将 {5,6} 加入set中,set变为{0, 1, 5 , 6}
遍历到11 set {0,1}
找set里面是否存在1, 存在,那么就返回true.
class Solution {
public boolean canPartition(int[] nums) {
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0,1);
int sum = 0;
for(int i=0;i<nums.length;i++)
{
sum += nums[i];
}
if(sum%2!=0)
return false;
boolean devide = false;
sum = sum/2;
for(int i = 0;i<nums.length;i++)
{
if(map.getOrDefault(sum-nums[i],0)==1)
{
devide = true;
break;
}
else
{
HashMap<Integer,Integer> m = new HashMap<>();
for(Integer k:map.keySet())
{
m.put(k+nums[i],1);
}
map.putAll(m);
}
}
return devide;
}
}