LC 416. 分割等和子集
题目描述
这是 LeetCode 上的 416. 分割等和子集(上) ,难度为 中等。
给你一个 只包含正整数 的 非空 数组 nums 。
请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:1
2
3
4
5输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:1
2
3
4
5输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
提示:
- 1 <= nums.length <= 200
- 1 <= nums[i] <= 100
前言
今天是我们讲解动态规划专题中的 「背包问题」的第二天。
在众多背包问题中「01 背包问题」是最为核心的,因此我建议你先精读过 背包问题 第一讲 之后再阅读本文。
另外,我在文章结尾处列举了我所整理的关于背包问题的相关题目。
背包问题我会按照编排好的顺序进行讲解(每 2~3 天更新一篇,确保大家消化)。
你也先可以尝试做做,也欢迎你向我留言补充,你觉得与背包相关的 DP 类型题目 ~
基本分析
通常「背包问题」相关的题,都是在考察我们的「建模」能力,也就是将问题转换为「背包问题」的能力。
由于本题是问我们能否将一个数组分成两个「等和」子集。
问题等效于能否从数组中挑选若干个元素,使得元素总和等于所有元素总和的一半。
这道题如果抽象成「背包问题」的话,应该是:
我们背包容量为 $target=sum/2$,每个数组元素的「价值」与「成本」都是其数值大小,求我们能否装满背包。
转换为 01 背包
由于每个数字(数组元素)只能被选一次,而且每个数字选择与否对应了「价值」和「成本」,求解的问题也与「最大价值」相关。
可以使用「01 背包」的模型来做。
当我们确定一个问题可以转化为「01 背包」之后,就可以直接套用「01 背包」的状态定义进行求解了。
注意,我们积累 DP 模型的意义,就是在于我们可以快速得到可靠的「状态定义」。
在 路径问题 中我教过你通用的 DP 技巧解法,但那是基于我们完全没见过那样的题型才去用的,而对于一些我们见过题型的 DP 题目,我们应该直接套用(或微调)该模型「状态定义」来做。
我们直接套用「01 背包」的状态定义:
$f[i][j]$ 代表考虑前 $i$ 个数值,其选择数字总和不超过 $j$ 的最大价值。
当有了「状态定义」之后,结合我们的「最后一步分析法」,每个数字都有「选」和「不选」两种选择。
因此不难得出状态转移方程:
/image_0.png)
代码:
1 |
|
- 时间复杂度:$target$ 为数组总和的一半,$n$ 数组元素个数。为共有 $n target$ 个状态需要被转移,复杂度为 $O(n target)$
- 空间复杂度:$O(n * target)$
「滚动数组」解法
在上一讲我们讲到过「01 背包」具有两种空间优化方式。
其中一种优化方式的编码实现十分固定,只需要固定的修改「物品维度」即可。
代码:
1 |
|
- 时间复杂度:$target$ 为数组总和的一半,$n$ 数组元素个数。为共有 $n target$ 个状态需要被转移,复杂度为 $O(n target)$
- 空间复杂度:$O(target)$
「一维空间优化」解法
事实上,我们还能继续进行空间优化:只保留代表「剩余容量」的维度,同时将容量遍历方向修改为「从大到小」。
代码:
1 |
|
- 时间复杂度:$target$ 为数组总和的一半,$n$ 数组元素个数。为共有 $n target$ 个状态需要被转移,复杂度为 $O(n target)$
- 空间复杂度:$O(target)$
总结
今天我们对昨天学的「01 背包」进行了应用。
可以发现,本题的难点在于对问题的抽象,主要考察的是如何将原问题转换为一个「01 背包」问题。
事实上,无论是 DP 还是图论,对于特定问题,大多都有相应的模型或算法。
难是难在如何将问题转化为我们的模型。
至于如何培养自己的「问题抽象能力」?
首先通常需要我们积累一定的刷题量,并对「转换问题的关键点」做总结。
例如本题,一个转换「01 背包问题」的关键点是我们需要将「划分等和子集」的问题等效于「在某个数组中选若干个数,使得其总和为某个特定值」的问题。
拓展
但这道题到这里还有一个”小问题“。
就是我们最后是通过「判断」来取得答案的。
通过判断取得的最大价值是否等于 $target$ 来决定是否能划分出「等和子集」。
虽然说逻辑上完全成立,但总给我们一种「间接求解」的感觉。
造成这种「间接求解」的感觉,主要是因为我们没有对「01 背包」的「状态定义」和「初始化」做任何改动。
但事实上,我们是可以利用「01 背包」的思想进行「直接求解」的。
因此在下一讲,我们还会再做一遍这道题。
不过却是以「另外一个角度」的「01 背包」思维来解决。
敬请期待 ~
最后
这是我们「刷穿 LeetCode」系列文章的第 No.416
篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!