力扣【416】分割等和子集

题目:

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

 

示例 1:

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

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

题解:

(快速对齐快捷键:Ctrl + Alt + L)

01背包问题——能不能装满容量为target的背包

本题要求把数组分成两个等和的子集,相当于找到一个子集,其和为sum/2,这个sum/2就是target

具体步骤如下:
1. 特例:如果sum为奇数,那一定找不到符合要求的子集,返回False。

2. dp[j]含义:有没有和为j的子集,有为True,没有为False。

3. 初始化dp数组:长度为target + 1,用于存储子集的和从0到target是否可能取到的情况。
比如和为0一定可以取到(也就是子集为空),那么dp[0] = True。

4. 接下来开始遍历nums数组,对遍历到的数nums[i]有两种操作,一个是选择这个数,一个是不选择这个数。
-不选择这个数:dp不变
-选择这个数:dp中已为True的情况再加上nums[i]也为True。比如dp[0]已经为True,那么dp[0 + nums[i]]也是True

5. 在做出选择之前,我们先逆序遍历子集的和从nums[i]到target的所有情况,判断当前数加入后,dp数组中哪些和的情况可以从False变成True。
(为什么要逆序,是因为dp后面的和的情况是从前面的情况转移过来的,如果前面的情况因为当前nums[i]的加入变为了True,比如dp[0 + nums[i]]变成了True,那么因为一个数只能用一次,dp[0 + nums[i] + nums[i]]不可以从dp[0 + nums[i]]转移过来。如果非要正序遍历,必须要多一个数组用于存储之前的情况。而逆序遍历可以省掉这个数组)

dp[j] = dp[j] or dp[j - nums[i]]
这行代码的意思是说,如果不选择当前数,那么和为j的情况保持不变,dp[j]仍然是dp[j],原来是True就还是True,原来是False也还是False;
如果选择当前数,那么如果j - nums[i]这种情况是True的话和为j的情况也会是True。比如和为0一定为True,只要 j - nums[i] == 0,那么dp[j]就变成了True。
dp[j]和dp[j-nums[i]]只要有一个为True,dp[j]就变成True,因此用or连接两者。
6. 最后就看dp[-1]是不是True,也就是dp[target]是不是True

import java.util.Arrays;

class Solution {
    public boolean canPartition(int[] nums) {
        //记住这个函数,创建数组的顺序流
        //返回值如果存在,则返回;否则返回orElse里的0
        //Arrays.stream(nums).sum();
        //Arrays.stream(nums).max().orElse(0);
        //Arrays.stream(nums).min().orElse(0);
        int sumAll = Arrays.stream(nums).sum();
        if (sumAll % 2 == 1) {
            return false;
        }
        //这里的除以2不要忘记,因为题目要求是数组中有数的和是sumAll/2就OK
        int target = sumAll / 2;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
//        如果是正序遍历,j++,nums[i]从头开始,则nums[i]会一直影响dp[j]的结果,
//        for (int i = 0; i < nums.length; i++) {
//            for (int j = nums[i]; j < target; j++) {
//                dp[j] = dp[j] || dp[j - nums[i]];
//            }
//        }
//        如果是逆序遍历,j--,nums[i]从头开始,则nums[i]只会影响一次dp[j]的结果
        for (int i = 0; i < nums.length; i++) {
            for (int j = target; j >= nums[i]; j--) {
                dp[j] = dp[j] || dp[j - nums[i]];
            }
        }
        return dp[target];
        return dp[target];
    }
}

/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        Solution s = new Solution();
        int[] nums = {1, 5, 11, 5};
        boolean res = s.canPartition(nums);
        System.out.println(res);
    }
}

为什么要用逆序遍历的解释:

解释一:

想了半天终于明白了为什么一维动态规划要逆序了。。。我也不知道自己说的好不好对不对。 dp[j] = dp[j] | dp[j - nums[i]] 实际上是用的 i - 1 层 dp[j] 和 dp[j - nums[i]] 得出的。 因此,如果顺序遍历的话, dp[j - num[i]] 会首先被更新成新的值, 然后再算 dp[j] = dp[j] | dp[j - nums[i]] 就不对了, 所以要逆序遍历。

解释二:

这里可能会有人困惑为什么压缩到一维时,要采用逆序。因为在一维情况下,是根据 dp[j] || dp[j - nums[i]]来推d[j]的值,如不逆序,就无法保证在外循环 i 值保持不变 j 值递增的情况下,dp[j - num[i]]的值不会被当前所放入的nums[i]所修改,当j值未到达临界条件前,会一直被nums[i]影响,也即是可能重复的放入了多次nums[i],为了避免前面对后面产生影响,故用逆序。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值