LeetCode 416.分割等和子集 【超详细解答】

1. 问题描述

题目
这是一道经典的0-1背包问题变种,但是更简单一些。首先我们回顾一下背包问题:

给你N个物品和一个可装载重量为W的背包,每个物品有重量和价值两个属性。其中第i个物品的重量为wt[i],价值为val[i],现在让你用这个背包装物品,能装的最大价值是多少?

那么我们这道题如何转换成上述的背包问题呢?

  1. N个数相当于N个物品;
  2. nums[i]相当于第i个物品的重量;
  3. 子集相当于从前i个物品中选取出的物品集合;
  4. 子集中元素之和相当于背包能装的重量。

转换后我们重新描述一下问题:

给你一个能装载sum/2重量的背包(sum为所有元素之和),给你N个物品(即原题中的N个元素),问能否从N个物品中选出一些物品(这些物品即为一个子集)使得背包 刚好装满(装满=子集元素和刚好等于某个值) ?

这样一看,是不是就是明显的背包问题?接下来我们来说如何求解。

2. 解法分析

2.1 状态和选择

状态:【背包容量】和【可选的物品】
选择:【装进背包】or【不装进背包】

2.2 dp数组的定义

经典0-1背包问题的dp数组定义是 dp[ i ][ j ],即对于前 i 个物品,放进容量为 j 的背包中,能够得到的最大价值。那么对于这道题目,可以解释为:
从前 i 个数字中,能否选取出一个子集,使得子集中的元素和刚好是 j 。那么dp[ i ][ j ]的取值就只有两种:true或false。
所以我们最终要求的答案就是dp[N][sum/2]。接下来确定一下base case:

  1. dp[0][…]为false,因为没有物品可选,所以背包肯定装不满,无法装满就为false;
  2. dp[…][0]为true,因为背包空间是0,0就是无法再装了,即装满了,为true。

2.3 状态转移的逻辑

根据上面dp数组的定义,通过进行【选择】,可得出以下状态转移:
注意i从1开始,故第i个物品的重量为nums[i-1]

  1. 如果不把 nums[i-1] 放入子集,或者说不把第 i 个物品放入背包中,那么背包能否刚好装满取决于上一个状态 dp[i-1][j],使用上次的结果;
  2. 如果把 nums[i-1] 放入子集,或者说把第 i 个物品放入背包中,那么背包能否装满,取决于状态 dp[i-1][j-nums[i-1]]。可以这么理解,如果背包的剩余容量 j-nums[i-1] 能被装满,那么把第i个物品放进去肯定也能装满,否则则是无法装满的。

2.4 处理边界情况

可以先对一些答案明显不能成立的情况进行处理,减少不必要的运算。(总和的一半记为target = sum/2

  1. 数组元素个数少于2个。(无法分割成两个子集,明显false)
  2. 数组元素总和为奇数。(奇数无法由两个相等的数相加得到,即无法分割成等和子集)
  3. 数组中的最大元素大于target,那么肯定无法得到两个 元素总和都为target的子集。

2.5 初步代码

public boolean canPartition(int[] nums) {
   
        int n=nums.length;
        if(n<2)
            return false;
        int sum=0,maxNum=Integer.MIN_VALUE;
        for(int i:nums)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值