背包类型问题
一、0-1 背包问题
基于labuladong的算法网站,0-1 背包问题;
题目描述:
给你一个可装载重量为 W 的背包和 N 个物品,每个物品有重量和价值两个属性。其中第 i 个物品的重量为 wt[i],价值为 val[i],现在让你用这个背包装物品,最多能装的价值是多少?
解题思路:
- 最重要的是状态和选择,做出什么选择导致了状态的变化;
- 明确dp数组记录什么
- dp[i][w] 表示:对于前 i 个物品(从 1 开始计数),当前背包的容量为 w 时,这种情况下可以装下的最大价值是 dp[i][w]。
代码:
/**
* 返回背包能背下的最大价值
*
* @param number:物品的个数
* @param maxWeight:背包所能承受的最大容量
* @param value:第i个物品的价值
* @param weight:第i个物品的重量
* @return
*/
int knapsack(int number, int maxWeight, int[] value, int[] weight) {
// dp[i][w]:i个物品时,承重为w,此时最大的价值
int[][] dp = new int[number + 1][maxWeight + 1];
// 遍历
// 状态一,物品个数
for (int i = 1; i <= number; i++) {
// 状态二,当前能承重、
for (int w = 1; w <= maxWeight; w++) {
// 如果当前背包所能承受的重量不足以放下第i个物品
if (w - weight[i - 1] < 0) {
dp[i][w] = dp[i - 1][w];
} else {
// 选择最大值
dp[i][w] = Math.max(dp[i - 1][w - weight[i - 1]] + value[i - 1], dp[i - 1][w]);
}
}
}
// 返回
return dp[number][maxWeight];
}
二、子集背包问题
基于labuladong的算法网站,子集背包问题;
力扣第416题,分割等和子集;
思路:
- 可以看成背包问题;
- 数组中所有的元素加起来,和即为目前背包能背的最大容量;
- 现在分割成两个相等的部分,那么可以看作背包的容量砍半;
[416]分割等和子集
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public boolean canPartition(int[] nums) {
int length = nums.length;
// 如果将这道问题看成背包问题,当数组是一个集合的时候,那么最大容量为
int maxWeight = 0;
for (int i = 0; i < length; i++) {
maxWeight += nums[i];
}
// 如果最大容量是奇数,不可能
if (maxWeight % 2 != 0) {
return false;
}
maxWeight /= 2;
// 现在将背包重量砍成一半,看是否能凑到
boolean[][] result = new boolean[length + 1][maxWeight + 1];
// base case
for (int i = 0; i <= length; i++) {
result[i][0] = true;
}
// 状态一:来到第i个元素
for (int i = 1; i <= length; i++) {
// 此时的重量为w
for (int w = 1; w <= maxWeight; w++) {
// 如果此时的重量小于当前元素
if (w - nums[i - 1] < 0) {
result[i][w] = result[i - 1][w];
} else {
// 放入or不放入
result[i][w] = result[i - 1][w - nums[i - 1]] || result[i - 1][w];
}
}
}
return result[length][maxWeight];
}
}
//leetcode submit region end(Prohibit modification and deletion)
三、完全背包问题
基于labuladongh的算法网站,经典动态规划:完全背包问题;
力扣第518题,零钱兑换 II;
[518]零钱兑换 II
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public int change(int amount, int[] coins) {
// 创建dp table
int length = coins.length;
// dp[i][j]:只采用0-第i枚硬币的组合条件下,能达到j金额的组合数
int[][] dp = new int[length + 1][amount + 1];
// base case
for (int i = 0; i <= length; i++) {
dp[i][0] = 1;
}
// 状态一:硬币不同
for (int i = 1; i <= length; i++) {
// 金额不同
for (int value = 1; value <= amount; value++) {
if (value - coins[i - 1] < 0) {
dp[i][value] = dp[i - 1][value];
} else {
// 这枚硬币我使用 or 我不使用
dp[i][value] = dp[i - 1][value] + dp[i][value - coins[i - 1]];
}
}
}
// 返回值
return dp[length][amount];
}
}
//leetcode submit region end(Prohibit modification and deletion)
四、动态规划和回溯算法到底谁是谁爹?
基于labuladong的算法网站,动态规划和回溯算法到底谁是谁爹?;
力扣第494题, 目标和;
[494]目标和
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
// 利用回溯
public int findTargetSumWays(int[] nums, int target) {
backtrack(nums, target, 0, 0);
return res;
}
int res = 0;
/**
* @param nums:整数数组
* @param target:目标和
* @param index:当前元素的位置
* @param sum:当前的结果
*/
void backtrack(int[] nums, int target, int index, int sum) {
// base case,到达数组最后一个位置时,让res=target
if (index == nums.length) {
if (sum == target) {
res += 1;
}
return;
}
// 开始选择
// 选择将第index位置的元素,加入
sum += nums[index];
backtrack(nums, target, index + 1, sum);
// 撤销之前的选择
sum -= nums[index];
// 选择将第index位置的元素,删除
sum -= nums[index];
backtrack(nums, target, index + 1, sum);
sum += nums[index];
}
}
//leetcode submit region end(Prohibit modification and deletion)