第九章 动态规划part04
● 01背包问题,你该了解这些!
● 01背包问题,你该了解这些! 滚动数组
● 416. 分割等和子集
正式开始背包问题,背包问题还是挺难的,虽然大家可能看了很多背包问题模板代码,感觉挺简单,但基本理解的都不够深入。
如果是直接从来没听过背包问题,可以先看文字讲解慢慢了解 这是干什么的。
如果做过背包类问题,可以先看视频,很多内容,是自己平时没有考虑到位的。
背包问题,力扣上没有原题,大家先了解理论,今天就安排一道具体题目。
详细布置
01背包问题 二维
https://programmercarl.com/%E8%83%8C%E5%8C%85%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%8001%E8%83%8C%E5%8C%85-1.html
视频讲解:https://www.bilibili.com/video/BV1cg411g7Y6
01背包问题 一维
https://programmercarl.com/%E8%83%8C%E5%8C%85%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%8001%E8%83%8C%E5%8C%85-2.html
视频讲解:https://www.bilibili.com/video/BV1BU4y177kY
416. 分割等和子集
本题是 01背包的应用类题目
https://programmercarl.com/0416.%E5%88%86%E5%89%B2%E7%AD%89%E5%92%8C%E5%AD%90%E9%9B%86.html
视频讲解:https://www.bilibili.com/video/BV1rt4y1N7jE
目录
01背包问题,你该了解这些!
package com.question.solve.leetcode.programmerCarl2._10_dynamicProgramming;
public class BagProblem0001 {
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagSize = 4;
testWeightBagProblem(weight, value, bagSize);
}
/**
* 动态规划获得结果
*
* @param weight 物品的重量
* @param value 物品的价值
* @param bagSize 背包的容量
*/
public static void testWeightBagProblem(int[] weight, int[] value, int bagSize) {
//创建dp数组
int goods = weight.length; //获取物品的数量
int[][] dp = new int[goods][bagSize + 1];
//初始化dp数组
//创建数组后,其中默认的值就是0
for (int j = weight[0]; j <= bagSize; j++) {
dp[0][j] = value[0];
}
//填充dp数组
for (int i = 1; i < weight.length; i++) {
for (int j = 1; j <= bagSize; j++) {
if (j < weight[i]) {
/**
* 当前背包的容量都没有当前物品i大的时候,是不放物品i的
* 那么前i-1个物品能放下的最大价值就是当前情况的最大价值
*/
dp[i][j] = dp[i - 1][j];
} else {
/**
* 当前背包的容量可以放下物品i
* 那么此时分两种情况:
* 1、不放物品i
* 2、放物品i
* 比较这两种情况下,哪种背包中物品的最大价值最大
*/
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
}
//打印dp数组
for (int i = 0; i < goods; i++) {
for (int j = 0; j <= bagSize; j++) {
System.out.print(dp[i][j] + "\t");
}
System.out.println("\n");
}
}
}
package com.question.solve.leetcode.programmerCarl2._10_dynamicProgramming;
import java.util.Arrays;
public class BagProblem0002 {
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagSize = 4;
testWeightBagProblem(weight, value, bagSize);
}
/**
* 初始化 dp 数组做了简化(给物品增加冗余维)。这样初始化dp数组,默认全为0即可。
* dp[i][j] 表示从下标为[0 - i-1]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
* 其实是模仿背包重量从 0 开始,背包容量 j 为 0 的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为 0。
* 可选物品也可以从无开始,也就是没有物品可选,即dp[0][j],这样无论背包容量为多少,背包价值总和一定为 0。
*
* @param weight 物品的重量
* @param value 物品的价值
* @param bagSize 背包的容量
*/
public static void testWeightBagProblem(int[] weight, int[] value, int bagSize) {
//创建dp数组
int goods = weight.length; //获取物品的数量
int[][] dp = new int[goods + 1][bagSize + 1]; //给物品增加冗余维,i = 0 表示没有物品可选
//初始化dp数组,默认全为0即可
//填充dp数组
for (int i = 1; i <= goods; i++) {
for (int j = 1; j <= bagSize; j++) {
if (j < weight[i - 1]) { // i - 1 对应物品 i
/**
* 当前背包的容量都没有当前物品i大的时候,是不放物品i的
* 那么前i-1个物品能放下的最大价值就是当前情况的最大价值
*/
dp[i][j] = dp[i - 1][j];
} else {
/**
* 当前背包的容量可以放下物品i
* 那么此时分两种情况:
* 1、不放物品i
* 2、放物品i
* 比较这两种情况下,哪种背包中物品的最大价值最大
*/
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]); // i - 1 对应物品 i
}
}
}
//打印dp数组
for (int[] arr : dp) {
System.out.println(Arrays.toString(arr));
}
}
}
01背包问题,你该了解这些!滚动数组
package com.question.solve.leetcode.programmerCarl2._10_dynamicProgramming;
public class BagProblem0003 {
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWight = 4;
testWeightBagProblem(weight, value, bagWight);
}
public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight) {
int wLen = weight.length;
//定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
int[] dp = new int[bagWeight + 1];
//遍历顺序:先遍历物品,再遍历背包容量
for (int i = 0; i < wLen; i++) {
for (int j = bagWeight; j >= weight[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
//打印dp数组
for (int j = 0; j <= bagWeight; j++) {
System.out.print(dp[j] + " ");
}
}
}
0416_分割等和子集
package com.question.solve.leetcode.programmerCarl2._10_dynamicProgramming;
public class _0416_分割等和子集 {
}
class Solution0416 {
public boolean canPartition(int[] nums) {
if (nums == null || nums.length == 0) return false;
int n = nums.length;
int sum = 0;
for (int num : nums) {
sum += num;
}
//总和为奇数,不能平分
if (sum % 2 != 0) return false;
int target = sum / 2;
int[] dp = new int[target + 1];
for (int i = 0; i < n; i++) {
for (int j = target; j >= nums[i]; j--) {
//物品 i 的重量是 nums[i],其价值也是 nums[i]
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
//剪枝一下,每一次完成內層的for-loop,立即檢查是否dp[target] == target,優化時間複雜度(26ms -> 20ms)
if (dp[target] == target)
return true;
}
return dp[target] == target;
}
}
class Solution0416_2 {
public static void main(String[] args) {
int num[] = {1, 5, 11, 5};
canPartition(num);
}
public static boolean canPartition(int[] nums) {
int len = nums.length;
//题目已经说非空数组,可以不做非空判断
int sum = 0;
for (int num : nums) {
sum += num;
}
//特判:如果是奇数,就不符合要求
if ((sum % 2) != 0) {
return false;
}
int target = sum / 2; //目标背包容量
//创建二维状态数组,行:物品索引,列:容量(包括 0)
/*
dp[i][j]表示从数组的 [0, i] 这个子区间内挑选一些正整数
每个数只能用一次,使得这些数的和恰好等于 j。
*/
boolean[][] dp = new boolean[len][target + 1];
//先填表格第 0 行,第 1 个数只能让容积为它自己的背包恰好装满(这里的dp[][]数组的含义就是“恰好”,所以就算容积比它大的也不要)
if (nums[0] <= target) {
dp[0][nums[0]] = true;
}
//再填表格后面几行
//外层遍历物品
for (int i = 1; i < len; i++) {
//内层遍历背包
for (int j = 0; j <= target; j++) {
//直接从上一行先把结果抄下来,然后再修正
dp[i][j] = dp[i - 1][j];
//如果某个物品单独的重量恰好就等于背包的重量,那么也是满足dp数组的定义的
if (nums[i] == j) {
dp[i][j] = true;
continue;
}
//如果某个物品的重量小于j,那就可以看该物品是否放入背包
//dp[i - 1][j]表示该物品不放入背包,如果在 [0, i - 1] 这个子区间内已经有一部分元素,使得它们的和为 j ,那么 dp[i][j] = true;
//dp[i - 1][j - nums[i]]表示该物品放入背包。如果在 [0, i - 1] 这个子区间内就得找到一部分元素,使得它们的和为 j - nums[i]。
if (nums[i] < j) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
}
}
}
for (int i = 0; i < len; i++) {
for (int j = 0; j <= target; j++) {
System.out.print(dp[i][j] + " ");
}
System.out.println();
}
return dp[len - 1][target];
}
/**
* //dp数组的打印结果
* false true false false false false false false false false false false
* false true false false false true true false false false false false
* false true false false false true true false false false false true
* false true false false false true true false false false true true
*/
}
class Solution0416_3 {
public boolean canPartition(int[] nums) {
//using 2-D DP array.
int len = nums.length;
//check edge cases;
if (len == 0)
return false;
int sum = 0;
for (int num : nums)
sum += num;
//we only deal with even numbers. If sum is odd, return false;
if (sum % 2 == 1)
return false;
int target = sum / 2;
int[][] dp = new int[nums.length][target + 1];
// for(int j = 0; j <= target; j++){
// if(j < nums[0])
// dp[0][j] = 0;
// else
// dp[0][j] = nums[0];
// }
//initialize dp array
for (int j = nums[0]; j <= target; j++) {
dp[0][j] = nums[0];
}
for (int i = 1; i < len; i++) {
for (int j = 0; j <= target; j++) {
if (j < nums[i])
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]);
}
}
//print out DP array
// for(int x : dp){
// System.out.print(x + ",");
// }
// System.out.print(" "+i+" row"+"\n");
return dp[len - 1][target] == target;
}
/**
* //dp数组的打印结果 for test case 1.
* 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
* 0, 1, 1, 1, 1, 5, 6, 6, 6, 6, 6, 6,
* 0, 1, 1, 1, 1, 5, 6, 6, 6, 6, 6, 11,
* 0, 1, 1, 1, 1, 5, 6, 6, 6, 6, 10, 11,
*/
}