算法Leetcode

解法:

主要考察两点:

①两个子集的元素和相等代表什么?

②0-1背包问题和完全背包问题的区别?

回到原题:

解决第一个问题:两个子集的元素和相等 => 任何一个子集的元素和等于整个数组元素和的一半。

所以当元素和非偶数时,断然不存在两个子集元素和相等。

接下来就是如何选择其中元素,以构成其和等于数组元素和的一半?

每个元素只能使用一次,很显然就是0-1背包问题。

时间复杂度:O(N * target)  其中,target为元素和的一半

代码: 细节问题:0-1背包问题得用逆序遍历。正序遍历会导致元素被多次使用,而逆序可以解决这个问题。因为当dp[i]时,dp[i-num] 还未被更新。

举个例子:nums = [2, 3],target = 5

①倒序遍历(√):

for (int num : nums) {
    for (int j = 5; j >= num; j--) {
        dp[j] = dp[j] || dp[j - num];
    }
}

当num = 2时:

dp[5] = dp[5] || dp[5-2];   (dp[5-2] = dp[3]  此时会用到dp[3],但是dp[3]还没被更新,所以不会用到num这个数字,这里就是倒序和正序的关键区别)

dp[4] = dp[4] || dp[4-2];

dp[3] = dp[3] || dp[3-2];

dp[2] = dp[2] || dp[2-2];  (正确

②正序遍历(×):

for (int num : nums) {
    for (int j = num; j <= 5; j++) {
        dp[j] = dp[j] || dp[j - num];
    }
}

当num = 2时:

dp[2] = dp[2] || dp[2-2];   

dp[3] = dp[3] || dp[3-2];

dp[4] = dp[4] || dp[4-2];   (此时dp[4-2] = dp[2]  这里dp[4]会再用一次num这个数字,而dp[2]已经用过num了,用到了两个num,就是完全背包,而不是0-1背包了)

dp[5] = dp[5] || dp[5-2]; 

完整代码:

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        int n = nums.length;
        for(int i = 0; i < n; i++){
            sum += nums[i];
        }

        if(sum % 2 == 1){
            return false;
        }

        int target = sum / 2;
        boolean[] dp = new boolean[target+1];

        dp[0] = true;

        for(int num : nums){
            for(int i = target; i >= 0; i--){
                if(i - num >= 0){
                    dp[i] = dp[i] || dp[i - num];
                }
            }
        }

        return dp[target];
    }
}

为了将代码更清晰展示,将此部分用if语句来写,当然直接在内循环用 i >= num 也可以。

for(int i = target; i >= 0; i--){
                if(i - num >= 0){
                    dp[i] = dp[i] || dp[i - num];
                }
            }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值