力扣416题 分割等和子集

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

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。


示例 2:

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

转换为 「0 - 1」 背包问题

本题与 0-1 背包问题有一个很大的不同,即:

  • 0-1 背包问题选取的物品的容积总量 不能超过 规定的总量;
  • 本题选取的数字之和需要 恰好等于 规定的和的一半。

「0 - 1」 背包问题的思路

作为「0-1 背包问题」,它的特点是:「每个数只能用一次」。解决的基本思路是:物品一个一个选,容量也一点一点增加去考虑,这一点是「动态规划」的思想,特别重要。

具体做法是:画一个 nums.length 行,target + 1 列的表格。这里 nums.length 是物品的个数,target 是背包的容量。nums.length 行表示一个一个物品考虑,target + 1多出来的那 1 列,表示背包容量从 0 开始考虑。很多时候,我们需要考虑这个容量为 0 的数值。

状态与状态转移方程

状态定义:a[i][j]表示从数组的 [0, i] 这个子区间内挑选一些正整数,每个数只能用一次,使得这些数的和恰好等于 j。
状态转移方程:很多时候,状态转移方程思考的角度是「分类讨论」,对于「0-1 背包问题」而言就是「当前考虑到的数字选与不选」。

  • 不选择 nums[i],如果在 [0, i - 1] 这个子区间内已经有一部分元素,使得它们的和为 j ,那么 dp[i][j] = true;
  • 选择 nums[i],如果在 [0, i - 1] 这个子区间内就得找到一部分元素,使得它们的和为 j - nums[i]。

状态转移方程:

a[i][j] = a[i - 1][j] or a[i - 1][j - nums[i]]

考虑初始化条件。

j - nums[i] 作为数组的下标,一定得保证大于等于 0 ,因此 nums[i] <= j;
注意到一种非常特殊的情况:j 恰好等于 nums[i],即单独 nums[i] 这个数恰好等于此时「背包的容积」 j,这也是符合题意的。

因此完整的状态转移方程是:                  

dp[i−1][j]至少是这个答案,如果 dp[i−1][j] 为真,直接计算下一个状态
dp[i][j]=   truenums[i] = j
dp[i−1][j−nums[i]].nums[i] < j

  

 

var canPartition = function(nums) {
    let sum = 0;

    for(let i = 0; i < nums.length; i++){
        sum = sum + nums[i];
    }
    // 数组中都是正整数 但总和为奇数时是无法分成两个相等正整数的 还有就是当数组中元素少于等于一个时是无法再分的 子集不可以是空数组也不可以是整个数组
    if(sum % 2 != 0 || nums.length <= 1){
        return false;
    }
    let target = sum/2;
    // 创建二维数组 初值为0
    const a = new Array(nums.length).fill(0).map(() => new Array(target+1).fill(0));
    // 定义边界 不能越界 一旦第一个数超过了target 说明指定返回false 因为这个数已经超过了整个数组和的一半 剩下的所有数加在一起也是比它小的
    if(nums[0] <= target){
        a[0][nums[0]] = true;
    }

    for(let i = 1; i < nums.length; i++){
        for(let j = 0; j <= target; j++){
            // 将上一行的结果复制下来,在下面更改需要的地方 nums[i]<j
            a[i][j] = a[i-1][j];
            // 
            if(nums[i] == j){
                a[i][j] = true;
                // continue;
            };
            // nums[i] < j
            if(nums[i] < j){
                a[i][j] = a[i-1][j] || a[i-1][j-nums[i]];
            }
        }
    }
    // 返回表格最右下角的值
    return a[nums.length-1][target];
};

 

空间优化

「0-1 背包问题」常规优化:「状态数组」从二维降到一维,减少空间复杂度。

  • 在「填表格」的时候,当前行只参考了上一行的值,因此状态数组可以只设置 2 行,使用「滚动数组」的技巧「填表格」即可;
  • 实际上,在「滚动数组」的基础上还可以优化,在「填表格」的时候,当前行总是参考了它上面一行 「头顶上」 那个位置和「左上角」某个位置的值。因此,我们可以只开一个一维数组,从后向前依次填表即可。
  • 「从后向前」 写的过程中,一旦 nums[i] <= j 不满足,可以马上退出当前循环,因为后面的 j 的值肯定越来越小,没有必要继续做判断,直接进入外层循环的下一层。相当于也是一个剪枝,这一点是「从前向后」填表所不具备的。
public class Solution {

    public boolean canPartition(int[] nums) {
        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];
    }
}


/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canPartition = function(nums) {
    let sum = 0;
    for(let i = 0; i < nums.length; i++){
        sum += nums[i];
    }
    if(sum % 2 != 0 || nums.length < 2){
        return false;
    }

     let target = sum/2;
    // 定义一维数组 初值为false
    const a = new Array(target+1).fill(0);
// 将a[0]赋为true
    a[0] = true;
    if(nums[0] <= target){
        a[nums[0]] = true;
    }

    for(let i = 1; i < nums.length; i++){
        for(let j = target; j >= nums[i]; j--){
            if(a[target] == true){
                return true;
            }
// a[j] = a[j]  是用来保留上一次循环已经确定下来为true的j 不再改变它
            a[j] = a[j] || a[j-nums[i]];
        }
    }
    return a[target];
};

 

复杂度分析

  • 时间复杂度:O(n×target),其中 n 是数组的长度,target 是整个数组的元素和的一半。需要计算出所有的状态,每个状态在进行转移时的时间复杂度为 O(1)。
  • 空间复杂度:O(target),其中 target 是整个数组的元素和的一半。空间复杂度取决于 dp 数组,在不进行空间优化的情况下,空间复杂度是 O(n×target),在进行空间优化的情况下,空间复杂度可以降到 O(target)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值