LeetCode89 - 分割等和子集
📝 题目描述
题目链接:分割等和子集
给你一个只包含正整数的非空数组 nums。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例:
1 | 示例 1: |
提示:
1 <= nums.length <= 2001 <= nums[i] <= 100
💡 解题思路
方法一:经典背包模板
先来回顾一下什么是传说中的背包问题:有 N 件物品和一个最多能容纳重量 W 的背包;第 i 件物品的重量是 weight[i],价值是 value[i];求解将哪些物品装入背包里物品价值总和最大;其中,如果每个物品能拿取无限多次,则叫作完全背包问题,若每个物品仅能拿取一次,则叫作01背包问题。
再来看这个题目,我们可以转化一下,先求出所有元素的和 sum,如果 sum 是偶数,我们令 target = sum/2,则问题转化为:
nums 数组代表物品集合,每个物品的重量是 nums[i],价值也是 nums[i],求背包容量为 target 时,能拿取的物品都最大价值总和是多少,如果能拿取的最大的价值总和也为 target,则可以返回 true。
也就是转化为了经典的01背包问题。
我们先按照经典的01背包问题模板做一下这道题目。
经典的01背包问题,我们可以采用二维基础解法:用 dp[i][j] 记录状态,行(也就是 i)代表物品,列(也就是 j)代表背包容量,逐步填表推导。
在开始讲解之前,先处理一下本题目的特殊情况:
1、先求出所有元素的和 sum 时,如果 sum 是奇数,则肯定不能分为两个相等的子集,直接返回 false 即可;
2、在求出所有元素的和 sum 时,我们可以同时记录一下数组中的最大元素 max_item,如果 max_item > target,也就是大于所有元素和的一半,那么无论将 max_item 放到两个结果集合中的任意一个,都会导致其大于所有元素和的一半,自然也无法满足题目结果,,直接返回 false 即可。
然后我们解释一下 dp[i][j] 的含义,其表示从前 i 个物品(注意i = 0 表示前 0 个物品,也就是不拿任何物品)中进行选择,在背包容量限制为 j 的情况下可拿走的物品的最大价值。
在定义状态之后,需要考虑边界情况。以下两种情况都属于边界情况。
- 当
i = 0时,表示前 0 个物品可以被选取,也就是不能选取任何物品,因此dp[0][j] = 0; - 题目中规定所有物品的重量都大于等于 1,那么如果背包容量
j = 0,则无法容纳任何物品,因此对于所有0≤i≤n,都有dp[i][0] = 0。
那么对于 i>0 且 j>0 的一般情况,如何确定 dp[i][j] 的值?对于第 i 个物品,面对当前的背包容量 j,我们只有两种选择:
- 不放第
i个物品(或者背包容量根本不够装,即j < nums[i]):此时的最大价值等同于在前i-1个物品中选的最大价值:
- 放入第
i个物品(前提是容量足够,即j >= nums[i]):我们需要先在背包中腾出nums[i]的空间,此时的最大价值等于前i-1个物品在容量为j-nums[i]时的最大价值,再加上当前物品的价值nums[i]。
综合起来,状态转移方程为:
最后,我们查看 dp[n][target] 的值是否等于 target,即可得到答案。
方法二:优化背包模板
首先,我们可以更换 dp 数组存储的数据类型,我们只需要存储 true 或 false 即可,没有必要存储具体的数值。
其次,可以发现在计算 dp 的过程中,每一行的 dp 值都只与上一行的 dp 值有关,因此只需要一个一维数组即可将空间复杂度降到 。此时的转移方程为:
这时,dp 的语意为:dp[j] 表示从前 i 个物品中任意挑选,能否凑出恰好为 j 的和,上述的状态转移方程可以理解为,想知道当前能否凑出和为 j,只需看以下两种情况只要有一种成立即可:
- 不选当前数字
num: 在之前的数字中,就已经能凑出j了(即旧的dp[j]为true)。 - 选当前数字
num: 在之前的数字中,能够凑出j - num(即旧的dp[j - num]为true),那么加上当前的num,自然就能凑出j了。
且需要注意的是第二层的循环我们需要从大到小计算,因为如果我们从小到大更新 dp 值,那么在计算 dp[j] 值的时候,dp[j−nums[i]] 已经是被更新过的状态,不再是上一行的 dp 值。
🔧 代码实现
1、经典背包模板
1 | class Solution { |
2、优化背包模板
1 | class Solution { |
📊 复杂度分析
1、经典背包模板
- 时间复杂度:,其中
n是数组的长度,target是整个数组的元素和的一半。需要计算出所有的状态,每个状态在进行转移时的时间复杂度为 。 - 空间复杂度:,空间复杂度取决于
dp数组,需要额外的 二维数组来存储计算状态。
2、优化背包模板
- 时间复杂度:,其中
n是数组的长度,target是整个数组的元素和的一半。需要计算出所有的状态,每个状态在进行转移时的时间复杂度为 。 - 空间复杂度:,空间复杂度取决于
dp数组,需要额外的 一维数组来存储计算状态。
🎯 总结
- 核心思想:学会经典的01背包问题的模板解法,以及记住题目的思路转换方法,把分割集合转换为背包问题。