LeetCode 416.分割等和子集

194 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目:给定一个只包含正整数的非空数组nums。判断是否可以将此数组分割成两个子集,使得两个子集的元素相等。

解题思路

首先题目我们不能纠结这两个子集是啥,我们只需要判断是否可以分割成功即可。那么思路就来了:

  • 两个子集之和元素相等,即两个子集相减等于0。即非空数组中在每个元素前加+或者-,使得最终的等式之和为0

上面这个思路和Leetcode 494目标和那题就很像了,只不过此处的目标和为0。那么按照那题的思路,我们直接来个回溯,对所有的可能进行判断,代码如下:

public boolean canPartition(int[] nums) {
    return canPartition(nums, 0, 0);
}

public boolean canPartition(int[] nums, int index, int curSum){
    if(index==nums.length){
        if(curSum==0){
            return true;
        }
        return false;
    }
    boolean add = canPartition(nums, index+1, curSum+nums[index]);
    boolean sub = canPartition(nums, index+1, curSum-nums[index]);
    return add || sub;
}

时间复杂度为O(2n)O(2^n),最终超出时间限制,但思路肯定是ok的。

思路转换

上述的两个子集,我们假设分别为pApB,那么就有:

pApB=0,pA+pB=sum,2pA=sumpA=sum2pA - pB = 0 , \\ pA + pB = sum, \\ 2pA = sum \rarr pA = \frac{sum}{2}

即找到nums中的一组元素,使得元素总和为sum2\frac{sum}{2},此处要求sum必须是偶数,如果是奇数则必定不可能组成。转换之后的问题即变成01背包问题,即背包总重量为sum2\frac{sum}{2},现要求在nums数组中寻找物品,使得物品总重刚好等于背包重量。则根据背包思路可得:

public boolean canPartition2(int[] nums){
    int sum = 0;
    for(int i=0;i<nums.length;i++){
        sum += nums[i];
    }
    if(sum % 2 == 1) return false;
    int weight = sum / 2;
    // dp[i][j]  0-i的物品装入重量j的背包可装方式有多少种
    // 如果i物品不装 dp[i][j] = dp[i-1][j]
    // 如果i物品装,则剩下i-1件物品要装入重量为j-nums[i]的背包中 dp[i][j] = dp[i-1][j-nums[i]
    // 则不超重量的前提下:dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]
    int[][] dp = new int[nums.length+1][weight + 1];
    for(int i=0;i<nums.length;i++){
        dp[i][0] = 1;
    }
    for(int i=1;i<=nums.length;i++){
        for(int j=1;j<=weight;j++){
            if(nums[i-1]>j){
                dp[i][j] = dp[i-1][j];
            }else {
                dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]];
            }
        }
    }
    if(dp[nums.length][weight]!=0) return true;
    return false;
}

背包空间优化,此时要注意的就是背包的顺序从原来的从小到达变成了从大到小,以此保证每个物品只被放入一次:

public boolean canPartition3(int[] nums){
    int sum = 0;
    for(int i=0;i<nums.length;i++){
        sum += nums[i];
    }
    if(sum % 2 == 1) return false;
    int weight = sum / 2;
    // dp[i] 物品装入重量为i的背包有多少种方式。
    int[] dp = new int[weight + 1];
    dp[0] = 1;
    for(int i=0;i<nums.length;i++){
        for(int j=weight;j>=nums[i];j--){
            dp[j] = dp[j] + dp[j-nums[i]];
        }
    }
    if(dp[weight]!=0) return true;
    return false;
}