题目
子集背包问题: 输入一个只包含正整数的非空数组nums,请你写一个算法,判断这个数组是否可以被分割成两个子集,使得两个子集的元素和相等。
思路分析
这道题是0-1背包问题的进阶版,思路也与它差不多。我们依旧采用'动态规划',状态是物品的个数和装载的目标重量。选择就和0-1背包问题一样了,在最后一个物品不能放下时就为就选择不放入,在最后一个物品能放下时就可以选择放入或不放入了。
最小子问题就是目标重量为零时和可选的物品个数为零时。
0-1背包问题解答的文章为下链接:
https://blog.csdn.net/Ostkakah/article/details/119453862?spm=1001.2014.3001.5501
代码展示
public boolean canPartition(int[] nums) {
int sum = 0;
for(int i = 0; i <= nums.length; i++) {
sum += nums[i];
}
sum /= 2;
boolean [][] dp = new boolean[nums.length + 1][sum + 1];
for(int i = 0; i < nums.length + 1; i++)//初始化
for(int j = 0; j < sum + 1; j++) {
if(i == 0) {
dp[i][j] = false;//当可选择的重物个数为零时结果值为false
}
if(j == 0) {
dp[i][j] = true;//当目标重量为零时结果为true
}
}
for(int i = 1; i < nums.length + 1; i++)
for(int j = 1; j < sum + 1; j++) {
if(j < nums[i - 1]) {//不够放下该重物时,其结果就为该重物之前的所以可选重物对应的Boolean值
dp[i][j] = dp[i - 1][j];
}else {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];//可以选择放也可以不放,两者都要判断
}
}
return dp[nums.length][sum];
}
此代码的空间复杂度太高了,我们可以对其进行'状态压缩'。
'状态压缩'后的代码为下:
public boolean canPartition1(int[] nums) {
int sum = 0;
for(int i = 0; i <= nums.length; i++) {
sum += nums[i];
}
sum /= 2;//计算出和的一半
boolean[] dp = new boolean[sum + 1];
for(int i= 0; i < sum + 1; i++)
dp[i] = false;//开始时可选的重物为零个,所以都为false
dp[0] = true;//唯独目标重量为0时为true
for(int i = 1; i < nums.length + 1; i++)
for(int j = sum; j >= 1; j--) {
if(j >= nums[i - 1]) {
dp[j] = dp[j] || dp[j - nums[i - 1]];
}//当j<nums[i - 1]就等于本身
}
return dp[sum];
}
其遍历方式为:从右向右左从上到下,由于是一维数组,为了知道'左上'的值我们必须从右向左,如果还是从左向右的话就会出现'左上'的值被覆盖。