给定一个只包含正整数的非空数组。
是否可以将这个数组分割成两个子集,
使得两个子集的元素和相等。
dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]
0到i中一些数和为j
0到i-1中一些数和为j
0到i-1中一些数和为j - nums[i] (此时选择nums[i] 和也就是j了)
目的还是和为j
背包问题:
列一个二维表格来理解
dp[1][6]能为true的关键是因为
0到i-1中一些数和为j - nums[i]
(此时选择nums[i] 和也就是j了)
目的还是和为j
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
此时dp[1][6] = dp[0][6] || dp[0][6-nums[1]];
//此时后面这个为true
dp[0][0]为true的合理性
本来容量为0的时候,应该考虑设置为false
但由于
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
j - nums[i]等于0时,j=nums[i],说明nums[i可以被单独分为一组
竖代表实际值的大小
横代表背包的实际容量
最后一个元素是5
T代表的是目前的背包容量的值可以有之前的实际值组合而成
代码:
public class Solution {
public boolean canPartition(int[] nums) {
int len = nums.length;
if (len == 0) {
return false;
}
int sum = 0;
for (int num : nums) {
sum += num;
}
// 特判:如果是奇数,就不符合要求
if ((sum & 1) == 1) {
return false;
}
int target = sum / 2;
// 创建二维状态数组,行:物品索引,列:容量(包括 0)
boolean[][] dp = new boolean[len][target + 1];
// 先填表格第 0 行,第 1 个数只能让容积为它自己的背包恰好装满
if (nums[0] <= target) {
dp[0][nums[0]] = true;
}
// 再填表格后面几行
for (int i = 1; i < len; i++) {
for (int j = 0; j <= target; j++) {
// 直接从上一行先把结果抄下来,然后再修正
dp[i][j] = dp[i - 1][j];
if (nums[i] == j) {
dp[i][j] = true;
continue;
}
if (nums[i] < j) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
}
}
}
return dp[len - 1][target];
}
}
还可继续优化
我们采用的二维数组,实际上,每一行的元素是根据上一行得出来的
变成一位数组即完成了优化
代码如下:
public class Solution {
public boolean canPartition(int[] nums) {
int len = nums.length;
if (len == 0) {
return false;
}
int sum = 0;
for (int num : nums) {
sum += num;
}
if ((sum & 1) == 1) {
return false;
}
int target = sum / 2;
boolean[] dp = new boolean[target + 1];
dp[0] = true;
if (nums[0] <= target) {
dp[nums[0]] = true;
}
for (int i = 1; i < len; i++) {
for (int j = target; nums[i] <= j; j--) {
if (dp[target]) {
return true;
}
dp[j] = dp[j] || dp[j - nums[i]];
}
}
return dp[target];
}
}