LeetCode Hot 100 No.416 分割等和子集

在这里插入图片描述
思路:动态规划
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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值