给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意: 每个数组中的元素不会超过 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
本题可以套用01背包:
1、背包的体积为sum / 2
2、背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值。
3、背包如果正好装满,说明找到了总和为 sum / 2 的子集。4、背包中每一个元素是不可重复放入。
动态规划五部曲:
1、dp数组及其下标 j 的意义:dp[j] 表示 容量为 j 的背包,能够凑成的最大子集和 ;
2、递推公式:dp[j] = Math.max(dp[j] , dp[j-nums[i]] + value[i])
3、初始化:dp[0]一定是0。
如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了。本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。
4、遍历顺序:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒叙遍历。
5、举例 dp 数组 :
dp[i]的数值一定是小于等于 i 的。如果dp[i] == i 说明,集合中的子集总和正好可以凑成总和 i,理解这一点很重要。
用例1,输入[1,5,11,5] 为例,如图:
最后dp[11] == 11,说明可以将这个数组分割成两个子集,使得两个子集的元素和相等。
java代码如下:
class Solution {
public boolean canPartition(int[] nums) {
if(nums.length == 0 || nums == null) 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--){
dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}