题意
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
思路分析
回溯法
分析
按照题目的要求,需要在给定的数组找出两个子集,使得这两个子集各自的和相等。一种方法可使用暴力法解,对给出的数组进行回溯,可以画出回溯的树状图的部分来分析:
递归方法中,若刚好等于原数组元素和一半则返回true,单层递归逻辑中的for循环遍历nums数组,这里还需要使用一个数组记录元素被使用的情况,被 之前的递归使用过的元素则将该元素标记,可以使用boolean数组。
回溯实现
/***
回溯实现方法
*/
private static void backTrack(int[] nums, boolean[] used, int sum, int arrSum) {
if (sum == arrSum / 2) {
res = true;
return;
}
for (int i = 0; i < nums.length; i++) {
if (!used[i]) {
used[i] = true;
backTrack(nums, used, sum + nums[i], arrSum);
used[i] = false;
}
}
}
但使用回溯法会超时。
01背包
引入& dp数组的含义
本题还可以使用01背包来解,并且使用一维数组解决。其中,dp[ j ] 代表从nums[0]~nums[i]中选择元素组成子集使得子集的元素和最大。
01背包中的0和1代表 放 与 不放 两种选择,并且 每个物品都只有一个 ,每个物品有 重量与价值,其中,重量是与背包相对应的,在理论篇中背包装的物品是有重量限制的,价值则与所求dp数组的含义相关,在理论篇中dp数组的含义即选取物品放入背包中,使得背包所背的物品的价值之和最大,求得的dp数组中的最后一个元素,即下标大小为背包限重的位置的dp数组元素,为所求的答案。
对应到本题中,则为:
nums数组中的每个元素不能被重复使用 对应 每个物品都只有一个
这里nums数组的元素即对应着理论中的物品;不能重复使用不代表nums数组不包含重复元素,就和不同的物品但是重量和价值都相同一样,这里的不重复是指数组中每个下标对应的元素是只有一个,同一下标的元素不能被多次使用
整个nums数组元素之和sum的一半: sum / 2 对应 背包的限重
要求的便是 能否找到部分元素和使得该元素和为 sum/2
nums数组的元素值 对应 物品的重量与价值
这不奇怪,上面讲了,重量与背包限重相关、价值与所求dp的含义相关。本题要求的是 数 ,背包限重对应本题的 sum/2这个 数,而dp的含义即将nums数组中的数取出组成子集使得子集的元素和最大,也是数。
求得dp[sum / 2] 若为sum/2则代表找到 对应 求得背包的物品最大值
本题使用01背包求得最大值,到此都和01背包的思路和实现都是吻合的,但题目要求 是否可以找到 因此则需要将数组最后一个元素与nums数组元素和的一半比较,两者相等则表示可以找到
理清楚上面01背包和本题的对应后,使用01背包来完成本题目则不难了,直接把模板套上即可。
递推公式
注意物品的重量和价值对应为nums数组的元素值。
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
初始化dp数组
使用一维数组时,可以无需初始化dp数组,默认值为0。
遍历顺序
和一维数组的01背包相同,先遍历物品再遍历背包,且背包从大到小倒叙遍历。
for(int i = 0; i < nums.length; i++) {
for(int j = sum; j > 0; j--) {
if(j >= nums[i]) {
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
}
举例推导dp数组
例子使用了nums[] = {1, 5, 11, 5}
完整Java代码实现
/***
01背包解法完整代码
*/
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for(int num : nums) sum += num;
if(sum % 2 != 0) return false;//sum为奇数则一定无法找到满足条件的两个子集
sum /= 2;
int[] dp = new int[sum + 1];
for(int i = 0; i < nums.length; i++) {
for(int j = sum; j > 0; j--) {
if(j >= nums[i]) {
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
}
return dp[sum] == sum;
}
}
/***
使用回溯法完整代码
*/
public class CutEqualSubUnion {
private static boolean res = false;
public static void main(String[] args) {
int[] nums = {1, 5, 11, 5};
boolean[] used = {false, false, false, false};
int arrSum = 0;
for (int n: nums) arrSum += n;
if(arrSum % 2 == 0) backTrack(nums, used, 0, arrSum);
System.out.println(res);
}
private static void backTrack(int[] nums, boolean[] used, int sum, int arrSum) {
if (sum == arrSum / 2) {
res = true;
return;
}
for (int i = 0; i < nums.length; i++) {
if (!used[i]) {
used[i] = true;
backTrack(nums, used, sum + nums[i], arrSum);
used[i] = false;
}
}
}
}
《代码随想录》刷题记