题目
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200
示例 1: 输入: [1, 5, 11, 5] 输出: true 解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2: 输入: [1, 2, 3, 5] 输出: false 解释: 数组不能分割成两个元素和相等的子集.
提示:
1 <= nums.length <= 200 1 <= nums[i] <= 100
思路
核心思路: 只要找到集合里面能够出现 sum/2
的子集总和,就算是可以分割成两个相同的元素和子集了。
01背包问题: 有N
件物品和一个最多能背重量为W
的背包。第i
件物品的重量是weight[i]
,得到的价值是value[i]
。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
其中一个商品可以重复多次放入就是完全背包,而只能放入一次就是01背包。
对于本题需要考虑以下四点,才能套用01背包:
- 背包的体积为
sum / 2
- 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
- 背包如果正好装满,说明找到了总和为
sum / 2
的子集 - 背包中每一个元素是不可重复放入
动规五部曲:
-
确定dp数组和下标含义
01背包中,dp[j]
表示: 容量为j
的背包,所背的物品价值可以最大为dp[j]
。
套到本题,dp[j]
表示背包总容量是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下标就要初始化为负无穷。
这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了,本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了 -
确定遍历顺序
因为使用的是一维dp数组,所以遍历物品的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历(个人巧记不喜勿Cue:把东西往包里放,即先物品,再背包,放是先放到最里面再往外放,所以倒着放) -
举例推导dp数组
如果dp[j] == j
表明,集合中的子集总数正好可以凑成总和j
以[1,5,11,5]为例:
java代码如下:
class Solution {
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0) return false;
int n = nums.length;
int sum = 0;
for(int num : nums){
sum += num;
}
//总和为奇数,不能平分
if(sum % 2 != 0) return false;
int target = sum / 2;
int[] dp = new int[target + 1];
for(int i = 0; i < n; i++){//先遍历物品
for(int j = target; j >= nums[i]; j--){//倒序遍历背包,物品 i 的重量是 nums[i],其价值也是 nums[i]
dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}