【背包问题系列】二、抽象背包问题
抽象背包问题
[题目描述]
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
[思路以及题目分析]
题目分析
- 两个元素和相等的子集,即每个子集的Sum-sum[1]=sum[1]=sum[2]=Sum/2,且所有值均为整数
- 所以可以优先排除一种情况-所有元素和为奇数的情况不满足
- 接下来的问题似乎看起来就可以很容易的抽象为背包问题了
- 有固定数量n的物品,总价值为total,是否能找到一种情况,选择物品满足物品和为 total/2
思路一:动态规划
-
本题毫无疑问可以使用暴力法进行所有方法匹配但是一定会TLE,也就不写这种办法了
-
在第一时间难以想象到dp方程的时候可以考虑递归方式进行参数确认
-
递归求解的时候,如果正向求解,则每次增加一个物品,然后判断前i是否能满足要求j(最大值不超过j)的最大价值
-
因此可以定义dp方程dp[i][j] 表示使用前i个元素组成不超过j最大价值
-
dp[i][j] = Math.max(dp[i - 1][j], j >= val ? dp[i - 1][j - val] + val : 0)
public boolean canPartition(int[] nums) {
int total = 0;
for (int n : nums
) {
total += n;
}
//corner case
if (total % 2 != 0) {
return false;
}
int target = total / 2 + 1;
int[][] dp = new int[nums.length][target];
//初始化边界情况,只取第一个元素的所有情况
for (int i = 0; i < target; i++) {
dp[0][i] = nums[0] < i ? nums[0] : 0;
}
for (int i = 1; i < nums.length; i++) {
int val = nums[i];
for (int j = 0; j < target; j++) {
//选或者不选的两种状态
dp[i][j] = Math.max(dp[i - 1][j], j >= val ? dp[i - 1][j - val] + val : 0);
}
}
return dp[nums.length - 1][target - 1] == target - 1;
}
时间复杂度O(n+n*total)
其余维度优化方式和【背包问题系列一】很相似
优化一 维度抽象成奇偶
public boolean canPartition(int[] nums) {
int total = 0;
for (int n : nums
) {
total += n;
}
//corner case
if (total % 2 != 0) {
return false;
}
int target = total / 2 + 1;
int[][] dp = new int[2][target];
//初始化边界情况,只取第一个元素的所有情况
for (int i = 0; i < target; i++) {
dp[0][i] = nums[0] < i ? nums[0] : 0;
}
for (int i = 1; i < nums.length; i++) {
int val = nums[i];
for (int j = 0; j < target; j++) {
//选或者不选的两种状态
dp[i & 1][j] = Math.max(dp[(i - 1) & 1][j], j >= val ? dp[(i - 1) & 1][j - val] + val : 0);
}
}
return dp[(nums.length-1)&1][target - 1] == target - 1;
}
优化二 维度抽象为一维
public boolean canPartition(int[] nums) {
int total = 0;
for (int n : nums
) {
total += n;
}
//corner case
if (total % 2 != 0) {
return false;
}
int target = total / 2 + 1;
int[] dp = new int[target];
for (int i = 0; i < nums.length; i++) {
int val = nums[i];
for (int j = target - 1; j >= 0; j--) {
int no = dp[j];
// 选第 i 件物品
int yes = j >= val ? dp[j - val] + val : 0;
dp[j] = Math.max(no, yes);
}
}
return dp[target - 1] == target - 1;
}
}