leetcode416. 分割等和子集 (0-1背包)

传送门

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

1.每个数组中的元素不会超过 100
2. 数组的大小不会超过 200

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

关于0-1背包图解表格: 这里

0-1背包基础上解答本题: 这里

相当于找到一组数字,使其和为数组和的2分之1 。

dp[i][j]:nums中0-i的数字 是否能组合成j

方法1. 二维动态规划
public boolean canPartition(int[] nums) {
    int sum = 0;
    for (int num : nums) sum += num;
    if (sum % 2 != 0) return false;
    sum /= 2;
    boolean[][] dp = new boolean[nums.length][sum + 1];
    // 第一行初始化 让i从1开始遍历 因为 方程里包含有dp[i-1][j]
    if (nums[0] <= sum)  dp[0][nums[0]] = true;
    for (int i = 1; i < nums.length; ++i) {
        for (int j = 0; j <= sum; ++j) {
            if (nums[i] == j) dp[i][j] = true;
            else if (nums[i] < j) {
                dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];//两个都是i-1!!!
            }
        }
    }
    return dp[nums.length - 1][sum];
}
方法2. 一维动态规划(列优化)

上面每次扫描一行之后,更新下一行;那么只用一个列大小的dp数组,每扫描一行,把下一行的dp[j]拉下来更新这一行。

这题要注意的是: 列的更新要从后往前定义

因为列的值代表要组成的和,更新它的时候: dp[j] = dp[j] || dp[j - nums[i]]; 要用到j-nums[i]的dp值,显然在表格中,它的dp值在j的dp值的左边(因为j-nums[i]肯定比j小,列从左到右增大);

但是注意的是,这个j-nums[i]的dp值是上一层的,如果dp[j]从左到右更新,在更新dp[j]时,dp[j-nums[i]]的值已经更新完了,那么它取到的dp[j-nums[i]]的值就是这一层的值,不是上一层的啦。

 	public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) sum += num;
        if (sum % 2 != 0) return false;
        sum = sum >> 1; 

        boolean[] dp = new boolean[sum + 1];
        dp[0] = true; // 使得下面j=nums[i]时 判true
        for (int i = 0; i < nums.length; ++i) {
            for (int j = sum; j >= 1; --j) { // j是目标和 倒序到1
                if (j >= nums[i])
                    dp[j] = dp[j] || dp[j - nums[i]];

                if (dp[sum]) return true; //提前返回
            }
        }
        return dp[dp.length - 1];
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值