简单背包问题
有一个容量为v的背包,从N件物品中选择一些放入背包中,每件物品的重量为w[i],价格为v[i].怎么选择合适的物品使得背包内物品的总价值最大.
分析
这个问题中每个物品可供选择的次数是0或1.即简单背包问题.
若可选择次数为n(n为一个有效的数字),则称之为有界背包问题.
若可选择次数无限,则称之为无界背包问题.
所有的背包问题都可以变换为简单背包问题.
在本题中,如果我们假设f[i][v]表示第i件物品放入背包后的最大总价值,那么有:
f[i][v] = max(f[i-1][v],f[i-1][v-w[i]]+v[i]);
第i件物品只有两种选择,放入或者不放入背包,如果不能放入背包,那么前i件物品的总价值就是前i-1件物品的总价值,等于f[i-1][v],而假设第i件物品放入了背包之中,前i件物品的总价值则为f[i-1][v-w[i]]+v[i]. v-w[i]即是前i-1件物品的重量.
该状态转移方程可压缩空间至:f[v] = max(f[v],f[v-w[i]]+v[i]).
所以该问题的解法可以看成,在1~N的外循环之中求所有f[0~v]的值,而所有f[v]的都依赖上一次主循环的当前值即f[v]和小于当前值的f[v-w[i]]+v[i].所以内循环采用逆序遍历,因为顺序遍历的话,f[v]的值已经改变,f[v+w[k]]的值如果依赖了f[v],那么f[v+w[k]]的值会出错.伪代码如下
for i = 1......n
for v = V......0
f[v] = max(f[v],f[v-w[i]]+v[i])
416. 分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 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
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
分析题目可知,将整个数组划分为两组,每组和等于 sum(nums)/2,而数组内的元素只能选择一次,类似于0,1背包问题. sum(nums)/2为背包容量v,nums[i]可看做物品的重量w[i].
代码如下:
public boolean canPartition(int[] nums) {
int size = nums.length;
int s = 0;
for (int num : nums) {
s += num;
}
if ((s & 1) == 1) {
return false;
}
int target = s / 2;
// 从第 2 行以后,当前行的结果参考了上一行的结果,因此使用一维数组定义状态就可以了
boolean[] dp = new boolean[target + 1];
// 先写第 1 行,看看第 1 个数是不是能够刚好填满容量为 target
for (int j = 1; j < target + 1; j++) {
if (nums[0] == j) {
dp[j] = true;
// 如果等于,后面就不用做判断了,因为 j 会越来越大,肯定不等于 nums[0]
break;
}
}
// 注意:因为后面的参考了前面的,我们从后向前填写
for (int i = 1; i < size; i++) {
// 后面的容量越来越小,因此没有必要再判断了,退出当前循环
for (int j = target; j >= 0 && j >= nums[i]; j--) {
dp[j] = dp[j] || dp[j - nums[i]];
}
}
return dp[target];
}
代码参考了题解:
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。