给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
本题
典型的背包问题,在n个物品中选出一定物品,填满sum/2的背包
F(n,c)考虑将n个物品填满容量为C的背包
F(i,c) = F(i-1,c) || F(i-1, c-w(i))
记忆化搜索
//记忆化搜索
//memo[i][c] 表示使用索引为[0...i]的这些元素,是否可以完全填充一个容量为c的背包
Integer[][] memo;
public boolean canPartition(int[] nums) {
int sum = 0;
for (int i = 0;i<nums.length;i++) sum+=nums[i];
if (sum%2!=0) return false;
memo = new Integer[nums.length][sum/2+1];
return tryPartition(nums, nums.length-1, sum/2);
}
//使用nums[0...index],是否可以完全填充一个容量为sum的背包
private boolean tryPartition(final int[] nums, int index, int sum){
if (sum==0) return true;
if (sum<0 || index<0) return false;
if (memo[index][sum] != null){
return memo[index][sum] == 1;
}
memo[index][sum] = (tryPartition(nums, index-1, sum) ||
tryPartition(nums, index-1, sum-nums[index])) ? 1:0;
return memo[index][sum]==1;
}
动态规划
public boolean canPartition(int[] nums) {
int sum = 0;
for (int i = 0;i<nums.length;i++) sum+=nums[i];
if (sum%2!=0) return false;
int n = nums.length;
int C = sum/2;
boolean[] memo = new boolean[C+1];
Arrays.fill(memo, false);
for (int i = 0;i<=C;i++)
memo[i] = (nums[0] == i); //这里初始化表示需要满才为true
for (int i = 1;i<n;i++)
for (int j = C;j>=nums[i];j--){
memo[j] = memo[j] || memo[j-nums[i]];
}
return memo[C];
}
背包问题补充
递归
//用 [0...index] 的物品,填充容积为c的背包的最大价值, w[] 为物品所占容量, v[]为物品价值
private int bestValue(int[] w, int[] v, int index, int c){
if (index<0 || c<=0) return 0;
int res = bestValue(w, v, index-1, c);
if (c>=w[index])
res = Math.max(res, v[index] + bestValue(w, v, index-1, c-w[index]));
return res;
}
private int knapsack01(int[] w, int[] v, int c){
int n = w.length;
return bestValue(w, v, n-1, c);
}
记忆化搜索
//用 [0...index] 的物品,填充容积为c的背包的最大价值, w[] 为物品所占容量, v[]为物品价值
Integer[][] memo;
private int bestValue(int[] w, int[] v, int index, int c){
if (index<0 || c<=0) return 0;
if (memo[index][c]!=null)
return memo[index][c];
int res = bestValue(w, v, index-1, c);
if (c>=w[index])
res = Math.max(res, v[index] + bestValue(w, v, index-1, c-w[index]));
memo[index][c]=res;
return res;
}
private int knapsack01(int[] w, int[] v, int c){
int n = w.length;
memo = new Integer[n][c+1];
return bestValue(w, v, n-1, c);
}
0-1背包
1. 未优化空间
时间复杂度:O(nC)
空间复杂度:O(nC)
private int knapsack01(int[] w, int[] v, int c){
int n = w.length;
int[][] memo = new int[n][c+1];
//此处是先计算的放入第一件物品的情况(也就是下标为0),然后再遍历1~n-1
//也可以初始化下标为0的情况,也就是背包为空的情况,然后遍历1~n
//关于初始化问题 背包九讲中有说明
for (int j = 0;j<=c;j++)
memo[0][j] = (j>=w[0] ? v[0] : 0);
for (int i = 1;i<n;i++){
for (int j = 0;j<=c;j++){
memo[i][j] = memo[i-1][j];
if (j>=w[i])
memo[i][j] = Math.max(memo[i][j], v[i]+memo[i-1][j-w[i]]);
}
}
return memo[n-1][c];
}
2. 优化空间为O(2*C)
时间复杂度:O(nC)
空间复杂度:O(2C)
//01背包优化1 空间
private int knapsack01(int[] w, int[] v, int c){
int n = w.length;
int[][] memo = new int[2][c+1];
for (int j = 0;j<=c;j++)
memo[0][j] = (j>=w[0] ? v[0] : 0);
for (int i = 1;i<n;i++){
for (int j = 0;j<=c;j++){
memo[i%2][j] = memo[(i-1)%2][j];
if (j>=w[i])
memo[i%2][j] = Math.max(memo[i%2][j], v[i]+memo[(i-1)%2][j-w[i]]);
}
}
return memo[(n-1)%2][c];
}
3. 优化空间为O©
由于每次更新只参考上边和左边的内容,所以可以从右向左来刷新这一行的内容。
//01背包优化2 空间
private int knapsack01(int[] w, int[] v, int c){
int n = w.length;
int[] memo = new int[c+1];
for (int j = 0;j<=c;j++)
memo[j] = (j>=w[0] ? v[0] : 0);
for (int i = 1;i<n;i++){
for (int j = c;j>=w[i];j--){
memo[j] = memo[j];
if (j>=w[i])
memo[j] = Math.max(memo[j], v[i]+memo[j-w[i]]);
}
}
return memo[c];
}
0-1背包问题更多变种
完全背包问题:每个物品可以无限使用。
多重背包问题:每个物品不止1个,有num(i)个。
多维费用背包问题:要考虑物品的体积和重量两个维度?
更加复杂:物品键假如更多约束(物品间可以互相排斥;也可以互相依赖)