LeetCode 416 分割等和子集java详解

10 篇文章 0 订阅
5 篇文章 0 订阅

LeetCode 416 分割等和子集

1.问题描述

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等

注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200

示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].

示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.

2.吹水

关于01背包问题或者更多衍生问题, 大家可以上网搜索《背包九讲》进行相关知识的学习。或者点击下方链接进行学习。
背包九讲

一般来讲,使用动态规划有5个步骤:
1)定义状态
2)思考状态转移方程(这一步是最难的)
3)思考初始化和输出
4)考虑状态压缩(即优化)

这道题实际上是一个典型的01背包问题

/**
 * 01背包问题
 * 题目
 * 有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i],
 * 求将哪些物品装入背包可使价值总和最大。
 */

相同之处在于每个元素(每个物品)仅有一个,可以选择放或者不放(放在第一个集合或者不放),不同在于01背包要求选取的容量不能超过规定的总量,这道题选取的数字恰好为整个数组的和的一半。

3.分析

第一步,定义状态

定义一个len行,target+1列的表格,这里的len表示数组元素的个数,target表示和。
target+1,多出来那一行是为了和从0开始到sum/2。
dp[i][j]:从数组[0,i]这个子区间中挑选一些数,每个数只用一次,判断是否刚好和为j

第二步、分析思考转移方程

01背包的一个特点就是物品一个个选,容量逐步变大,对于每个元素nums[i]来说,都有两种选择:选or不选。
不选择nums[i],这说明无论有没有第i个元素,都可以在[0,i-1]区间内凑成和为j。
选择nums[i],那么就需要在[0,i-1]这个区间中找到一部分元素,使他们和为j-nums[i]。
因此状态转移方程为:
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]

第三步,思考初始化和输出
1)j-nums[i] 是数组的下标,理应建立在 j >= nums[i] 的情况下,如果 j 恰好等于 nums[i] ,那么应该理解为元素 nums[i] 刚好可以装满容量为 j 的背包,这是符合题意得。
2)dp[0][0] = true 按照题意,容量为 0 得时候,本应该设为false,但是考虑到存在 j == nums[i] 时,nums[ i ] 刚好可以作为一组被分割得元素,其他元素作为另一组,则需要dp[0][0] = true
3)输出显然为表格的最后一格
在这里插入图片描述

代码详解

   if(nums==null || nums.length==0)
            return false;
        int n = nums.length;
        int sum = 0;
        for (int num : nums)
            sum += num;
        //奇数肯定不能分成相等的两份
        if ((sum & 1) == 1)
            return false;
        int target = sum / 2;
        //dp[i][j]:前i个物品能否让容量为j的背包填满
        boolean[][] dp = new boolean[n][target + 1];
        dp[0][0] = true;
        //第一个数只能让刚好容量为nums[0]填满
        if (nums[0] <= target)
            dp[0][nums[0]] = true;
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= target; j++) {
                //如果不用第i个数的和可以为j 那么多了第i个数也无所谓
                dp[i][j] = dp[i - 1][j];
                if (nums[i] <= j)
                    //如果用第i个数
                    dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
                //只要最后一列为true 最后一格肯定为true
                if(dp[i][target])
                    return true;
            }
        }
        return dp[n-1][target];
    }

4)思考状态压缩

01背包常规优化:将状态数组从二维压缩到一维:
在填表格的时候,我们每次都参照了上一行中的两个元素(具体是左上角和头顶上),因此可以将二维降到一维。
需要注意的是,当我们使用二维的时候,j可以顺序也是可以是逆序,但使用一维数组的时候,j必须是逆序。
一旦发现j<=nums[i],我们可以退出循环,因为后面的j只会越来越小,数组下标不能出现负数的情况。
关于逆序:使用一维数组,如果从前往后会影响后面的数,比如nums={2,2,3,5},target=6
i=0时,dp[2]=true
i=1 j=4 nums[i]=2;
dp[4] = dp[4] || dp[4-2]=true
dp[6] = dp[6] || dp[6-4]=true显然是错误的。
 if(nums==null || nums.length==0)
            return false;
        int len = nums.length;
        int sum = 0;
        for (int num : nums)
            sum += num;
        //奇数不能分成和相等的两部分
        if ((sum & 1) == 1)
            return false;
        int target = sum / 2;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
        //第一个数只能让容积为他自己的背包装满
        if (nums[0] <= target) {
            dp[nums[0]] = true;
        }

        for (int i = 1; i < len; i++) {
            for (int j = target; nums[i] <= j; j--) {
                if (dp[target])
                    return true;
                dp[j] = dp[j] || dp[j - nums[i]];
            }
        }
        return dp[target];
    }

最后
希望读者可以debug完整走一遍,01背包问题是一类非常重要的动态规划问题,初学的时候会比较陌生,但是多debug!!自己画一个表格就会懂了!!最后希望大家点赞支持一下!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值