代码随想录笔记
AcWing-背包九讲专题
一道例题
dd大牛背包9讲
背包笔记
对于面试的话,其实掌握01背包,和完全背包,就够用了,最多可以再来一个多重背包。
01背包:n种物品,每种物品只有 1 个,每个物品只有选OR不选两种情况
完全背包:n种物品,每种物品有 无限 个,每个物品可选无限次
多重物品:n种物品,每种物品的个数各不相同,每个物品有限度
混合背包问题:
二位费用的背包问题:
分组背包
01 背包 :每种物品仅有一件,可以选择放或不放。
二维数组
题目:有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
例子:背包最大重量为4
-
暴力法
每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是 o ( 2 n ) o(2^n) o(2n),这里的n表示物品数量。 -
二维dp数组01背包(dp五步)
- 首先对于整个问题:m个物品,背包容量最大为n。
初步将问题分解为:在已经知道了前m-1个物品的所有最优解的情况下(即无论背包容量多少),再加上第m个物品的情况。
此时有三种情况:
1、第m个的重量超过了背包容量n,不可能放下第m个,所以此时的最优解,就是m-1个物品,背包容量为n的最优解。
2、第m个的重量小于n,可以放但是不放,此时的最优解仍然是m-1个物品,背包容量为n的最优解。
3、第m个的重量小于n,可以放而且真的放了,此时最优解就是第m个物品的价值,加上,m-1个物品时背包容量为(n-第m个物品的重量)的最优解。(第一点保证了第三点j-weight[i]
一定大于零)
根据递推关系发现,构造问题的最优解,不仅需要一个i来标识选择物品的范围,还需要一个j,来求出在0到i选择物品且背包容量为j时的最优解。从而将dp数组确定为dpij
(1)确定dp数组以及下标的含义
dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
(2)递推公式
不放物品i:dp[i-1][j]
放物品i : dp[i-1][j-weight[i]] + value[i]
(不放物品 i 时,背包重量 dp[i-1][j-weight[i]]
)
递推公式: dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])
j-weight[i]
在干什么?在保证装入 物品 i 之前有充分的背包空间。
(3)初始化
- 一个例题
初始化,
首列:全为零,因为背包容量为0的时候,什么都装不了,价值为0
首行:如果表示物品,只放该物品时的价值为value[物品](前提是背包的容量要够);如果不表示物品,初始化全为0
其余全部初始化为0
(4)遍历顺序
对于二维数组实现01背包,双层for循环的条件哪个在外都可以
一维数组(滚动)
- 以下面为例
- 代码
//代码随想录
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] + " ");
}
}
- debug打断点+手写
第二个for循环的条件j >= weight[i];
直接保证当前i可以放进去
逆序,为了之前数组的值不被污染。
dp[j]
容量为 j 的背包,拥有的最大价值为 dp[j]
- 递推公式:
dp[j] = max(dp[j]) ,dp[j-weight[i]] + value[i] )
不放物品 i :dp[j] 【对比】二维数组中不放物品i是 dp[i-1][j]
放物品i:dp[j-weight[i]] + value[i]
- 初始化:dp[0] = 0
- 遍历顺序:先物品后背包;且要倒序遍历背包。倒序遍历才能保证物品只添加一次
卡码 46. 携带研究材料
题目链接: https://kamacoder.com/problempage.php?pid=1046
- AC-二维数组版
import java.util.*;
public class Main {
public static void main(String[] args) {
// 背包容量 N
// 物品种类 M
Scanner sc = new Scanner(System.in);
int M = sc.nextInt();
int N = sc.nextInt();
int[] values = new int[M];
int[] weights = new int[M];
for(int i = 0; i < M;i++) {
weights[i] = sc.nextInt();
}
for(int i = 0; i < M;i++) {
values[i] = sc.nextInt();
}
int[][] dp = new int[M][N+1];
// 初始化
for(int i = weights[0]; i <= N; i++) {
dp[0][i] = values[0];
}
// 先物品
for(int i = 1; i < M; i++) {
// 后背包
for(int j = 0; j <= N; j++) {
if(j-weights[i] < 0) {
dp[i][j] = dp[i-1][j];
} else {
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weights[i]] + values[i]);
}
}
}
System.out.println(dp[M-1][N]);
}
}
- 出错点
初始化错误:背包初始化是int[][] dp = new int[M][N+1];
而不是int[][] dp = new int[M][N];
,因为 列代表 背包的容积是 0-N。
所以返回值、初始化也有错 - 这里的双层for循环哪个在外都可以。for物品 for背包容量,对于二维数组实现的0-1背包,是可以颠倒的。如下图,他的值是由左上方或者正上方得出来的
- ac-滚动数组版
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int M = sc.nextInt();
int N = sc.nextInt();
int[]weight = new int[M];
int[]value = new int[M];
int[]dp = new int[N+1];
for(int i=0; i<M; i++) {
weight[i] = sc.nextInt();
}
for(int i=0; i<M; i++) {
value[i] = sc.nextInt();
}
for(int i=0; i<M; i++) {
for(int j=N; j>=weight[i]; j--) {
dp[j] = Math.max(dp[j], dp[j-weight[i]]+value[i]);
}
}
System.out.println(dp[N]);
}
}
416. 分割等和子集
请问这道题和动态规划有啥关系呢?
思路(卡哥的思路):
① 回溯,每个元素取或者不取,时间复杂度是指数级别的
②动态规划
以示例1为例,数组求总和22,和的一半11就是两个子集分别的和。
问容量为11的背包,能不能被[1,5,11,5]装满,如果装满了,就说明可以组成11,如果装不满,就说明不能组成11。如果能凑成11,那么剩下的元素肯定也能凑成另一个11。
背包问题的关键在元素可不可以重复使用,这里不可以,所以是0-1背包。每一个元素相当于一个物品,物品的重量和价值都是它的值。
- 看了思路之后自己通过了
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for(int i=0; i<nums.length; i++) {
sum += nums[i];
}
if(sum % 2 != 0) return false; //不可能是奇数,因为要划分成两个数组。
// M = nums.length
// N = sum/2
// weight[i] = nums
// value[i] = nums
int M = nums.length;
int N = sum/2;
int[]dp = new int[N+1];
for(int i=0; i<M; i++) {
for(int j=N; j>=nums[i]; j--) {
dp[j] = Math.max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
if(dp[N] == N) {
return true;
} else {
return false;
}
}
}
- 补充 特判:如果是奇数,就不符合要求
Java
- 导入
import java.util.*; 把util这个包下的全部类导入到程序中
- 输入
Scanner sc = new Scanner(System.in);
int M = sc.nextInt();
int N = sc.nextInt();
int[] values = new int[M];
int[] weights = new int[M];
for(int i = 0; i < M;i++) {
weights[i] = sc.nextInt();
}
for(int i = 0; i < M;i++) {
values[i] = sc.nextInt();
}
- 输出
System.out.println();
- main函数为入口
public class Main {
public static void main(String[] args) {
//
}
}