一、1049. 最后一块石头的重量 II
1.思路:01背包问题,其中dp[j]表示容量为j的背包,最多可以背最大重量为dp[j]。
2.注意:递推公式dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);本题中的重量就是价值,所以第二个stone[i]表示价值的意思; 遍历顺序上仍然是先物品后背包
3.本题与分割等和子集类似,不同就在于最后return时,本题得到的target = sum / 2 因为是向下取整,所以sum - dp[target] 一定是大于等于dp[target]。
所以相撞也就是将target与sum - dp[target]作差即可。
class Solution {
public int lastStoneWeightII(int[] stones) {
if (stones.length == 0 || stones == null)
return 0;
int sum = 0;
// 先求出这堆石头的和,以便得到背包能背的最大重量
for (int stone : stones) {
sum += stone;
}
int target = sum >> 1;
int[] dp = new int[target + 1];
// for循环, 先物品再背包
for (int i = 0; i < stones.length; i++) {
// 这里的内循环一定是j >= stone[i] ,否则无法判断第二个max条件
for (int j = target; j >= stones[i]; j--){
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
}
}
return sum - 2 * dp[target];
}
}
二、完全背包
1.有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
2.核心代码:区别于01背包的一维滚动数组,差别就是内循环
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
3.计算过程
3.518. 零钱兑换 II
1.思路:完全背包。
2.递推公式:dp[j] += dp[j - nums[i]],表示填满j(包括j)这么大容积的包,有dp[j]种方法。
例如:dp[j],j 为5,
- 已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
- 已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
- 已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
- 已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 容量为5的背包
- 已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包
那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。
3.注意:该题纯完全背包是能凑成总和就行,不用管怎么凑的,不需要管顺序。
4.代码:
class Solution {
public int change(int amount, int[] coins) {
// dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
int[] dp = new int[amount+1];
//初始化dp数组,表示金额为0时只有一种情况,也就是什么都不装
dp[0] = 1;
for (int i = 0; i < coins.length; i++) { // 零钱的种类数
for (int j = coins[i]; j <= amount; j++){ // 组合方法
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
}