public static int max_back() { // 0-1 背包优化,使用一维数组来实现
int[] weight = new int[]{2, 2, 6, 5, 4};
int[] value = new int[]{6, 3, 5, 4, 6};
int Max = 10; // 最大容量
int n = 5; // 物品件数
int[] dp = new int[Max+1];
dp[0] = 0;
for(int i = 0; i < n; i++) {
for(int j = Max; j >= weight[i]; j--) {
dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);
}
}
return dp[Max];
}
为什么for循环里面的第二个循环要从后往前遍历? 这是因为如果从前往后遍历的话,后面再次判断的时候就会将前面已经求出的值添加进去。 可能不太好理解,这里举个例子,就从 i = 0 的时候这个例子,如果 j 从weight[i] 开始往后走的话,首先dp[j] 是肯定会被赋值 value[i] 的,这时,随着 j 的值的增加,继续判断,当 j = 2*weight[i] 的时候,注意,这个时候dp[j] 将会被赋值为 两倍的 value[i] (仔细想想) , 然而,这是肯定不符合 0-1 背包的定义的。
leetcode:416 分割等和子集 https://leetcode-cn.com/problems/partition-equal-subset-sum/
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
前期思路:求出数组中所有整数之和sum,判断是否能分成两个子集,转化为判断是否存在部分数组元素之和等于sum/2
public boolean canPartition(int[] nums) {
int len = nums.length;
if(len <= 1) return false;
int sum = 0;
for(int i = 0; i < len; i++) sum += nums[i];
if(sum%2 == 1) return false;
sum /= 2;
int[] dp = new int[sum+1];
// 转化为 0-1 背包问题,sum此时就代表背包的容量
// 即判断 是否容量为sum时,能够达到sum !
// 这里 价值与重量 相等!
dp[0] = 0;
for(int i = 0; i < len; i++) {
for(int j = sum; j >= nums[i]; j--) {
dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
}
}
return dp[sum] == sum;
}
将判断数组中是否存在一部分整数之和等于sum/2,转化为0-1背包问题。
一部分整数之和,对应的就是对于一个整数,我们可以选择放到背包中和不放到背包中。
最后,只需要判断,当背包容量为sum/2时,背包中的最大价值(重量?, 这里应该都是一样的) 是否可以达到sum/2
另一种解法:
class Solution {
public boolean canPartition(int[] nums) {
int len = nums.length;
if(len <= 1) return false;
int sum = 0;
for(int i = 0; i < len; i++) sum += nums[i];
if(sum%2 == 1) return false;
sum /= 2;
// 动态规划,0-1背包,求使得nums和为sum的
boolean[] dp = new boolean[sum+1];
// dp[i] 代表是否能够凑成大小为 i 的数
dp[0] = true;
for(int i = 0; i < len; i++) {
for(int j = sum; j >= nums[i]; j--) {
dp[j] = dp[j] || dp[j-nums[i]];
// j == nums[i]时,dp[0] = true, 必能凑成
}
}
return dp[sum];
}
}
----------------更新
leetcode : 494题 目标和
发现做了上面两道题,碰到类似的题目还是有点转不过来。。。
class Solution {
public int findTargetSumWays(int[] nums, int S) {
// 动态规划解答
int sum = 0;
int len = nums.length;
if(len == 0) return 0;
for(int i = 0; i < len; i++) sum += nums[i];
sum += S;
if(sum %2 == 1 || 2*S > sum) return 0;
sum /= 2;
int[] dp = new int[sum+1];
// dp[i] 代表能凑成 i 有多少中凑法
dp[0] = 1;
for(int i = 0; i < len; i++) {
for(int j = sum; j >= nums[i]; j--) {
dp[j] = dp[j] + dp[j-nums[i]];
}
}
return dp[sum];
}
}