题目
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注:注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200
示例1
- 输入: [1, 5, 11, 5]
- 输出: true
- 解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例2
- 输入: [1, 2, 3, 5]
- 输出: false
- 解释: 数组不能分割成两个元素和相等的子集.
思路
初步看这个题是要把给定数组分成两个子集,使其总和相等,那么我们只需要在集合中找出sum/2
的子集总和,就可以分割成相同元素和子集了。
!那么这道题就很适合用01背包来做了,这道题中我们的元素只能取一次,首先我们可以把背包大小定为我们所需要寻找的sum/2
的子集和,其给定数组的元素的重量和价值都是数组元素值,我们只需写出递推公式,然后再去查看是否有子集能填满容量sum/2
。
总结一下便是以下四点:
背包的体积为
sum/2
背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
背包如果正好装满,说明找到了总和为sum/2
的子集。
背包中每一个元素是不可重复放入。
动态规划五步曲
- 确定dp数组以及下标的含义
01背包中,dp[j]表示: 容量为j的背包,所背的物品价值最大可以为dp[j]。本题中每一个元素的数值既是重量,也是价值。
套到本题,dp[j]表示 背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]。
- 确定递推公式
01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。
所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
- dp数组如何初始化
从dp[j]的定义来看,首先dp[0]一定是0。
如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。
这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了。
- 确定遍历顺序
如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!
1 | for(int i = 0; i < nums.length; i++){ |
- 举例推导dp数组
如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j,理解这一点很重要。
注:dp[j]的数值一定是小于等于j的。
用例1,输入[1,5,11,5] 为例,如图:
最后dp[11] == 11,说明可以将这个数组分割成两个子集,使得两个子集的元素和相等。
代码实现-一维dp数组
1 | class Solution { |
代码实现-二维dp数组
看不懂的话详见动态规划-01背包理论基础(一)
1 | class Solution { |