01背包
牛客链接【模板】01背包_牛客题霸_牛客网 (nowcoder.com)
我们都根据几步进行分析
1.状态表示
2.状态转移方程
3.初始化
4.填表顺序
5.返回值
根据以上来分析,并由此进行空间优化
01背包第一问
1.状态表示
根据一些题目的经验
我们设置dp[i]来表示在前i个物品里,选出最大价值(发现无法表示体积)所以应该是要用二维dp来表示,因为没有变量来表示体积的状态
因此使用dp[i][j]表示在前i个物品里,体积为j里,选出最大价值
2.状态转移方程
我们以i选不选来分类讨论
但是要注意一些细节问题!!!
首先就是需要j - v[i]一定要满足 >= 0,不然会发生越界情况
这样就可以表示出所有的状态,取两者最大值填入表中即可
3.初始化
第一行可以很清楚的知道,因为从0个物品里选择总体积为j的最大价值,都是填0
第一列,从i个物品里选择总体积为0的最大价值,也都填0,就不需要进行初始化了,直接创建即可
4.填表顺序
根据状态转移方程可以看出时从上到下填写表
从左到右从右到左都可以,我们习惯从左向右(和后面的优化有一定的关系)
5.返回值
直接返回dp[n][v]
n是前n个物品,v是背包体积
01背包第二问
1.状态表示
和上一题一样
2.状态转移方程
和上一题一模一样,但是多了一个当无法满足体积要求的状态
细节!!!
1.我们使用dp[i][j] == -1 来表示这个体积无法在前i个物品中选出
2.首先就是需要j - v[i]一定要满足 >= 0,不然会发生越界情况,其次就是需要前一个体积必须要存在,所以前一个体积不能等于 -1
3.初始化
第一行可以很清楚的知道,因为从0个物品里选择总体积为j的最大价值,除了第一个以外,其他的0个物品中选择为j体积都无法达到,所以要等于-1
第一列,从i个物品里选择总体积为0的最大价值,都填0,就不需要进行初始化
4.填表顺序
和前一问一样
5.返回值
直接返回dp[n][v]
n是前n个物品,v是背包体积
可以写出代码
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();//数量
int v = sc.nextInt();//背包体积
int[][] arr = new int[n + 1][2];//存储物品体积0与价值1
for(int i = 1;i <= n;i++){
arr[i][0] = sc.nextInt();
arr[i][1] = sc.nextInt();
}
int[][] dp = new int[n + 1][v + 1];
for(int i = 1;i <= n;i++){
for(int j = 1;j <= v;j++){
dp[i][j] = dp[i - 1][j];
if(j - arr[i][0] >= 0){
dp[i][j] = Math.max(dp[i][j],dp[i - 1][j - arr[i][0]] + arr[i][1]);
}
}
}
System.out.println(dp[n][v]);
for(int i = 1;i <= n;i++){
Arrays.fill(dp[i],0);
}
for(int i = 1;i <= v;i++){
dp[0][i] = -1;
}
for(int i = 1;i <= n;i++){
for(int j = 1;j <= v;j++){
dp[i][j] = dp[i - 1][j];
if(j - arr[i][0] >= 0 && dp[i - 1][j - arr[i][0]] != -1){
dp[i][j] = Math.max(dp[i][j],dp[i - 1][j - arr[i][0]] + arr[i][1]);
}
}
}
System.out.println(dp[n][v] == -1 ? 0 : dp[n][v]);
}
}
空间优化
由于每次填表的时候只需要用到前一列的变量其他的不需要使用,并且是只需要用到左上方的变量,我们可以使用滚动数组的方式来进行优化
1.遍历需要从右向左
2.将dp的行给删掉
3.不要去解释优化后的变量,自己删一遍感受一下
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();//数量
int v = sc.nextInt();//背包体积
int[][] arr = new int[n + 1][2];//存储物品体积0与价值1
for(int i = 1;i <= n;i++){
arr[i][0] = sc.nextInt();
arr[i][1] = sc.nextInt();
}
int[] dp = new int[v + 1];
for(int i = 1;i <= n;i++){
for(int j = v;j >= arr[i][0];j--){
dp[j] = Math.max(dp[j],dp[j - arr[i][0]] + arr[i][1]);
}
}
System.out.println(dp[v]);
Arrays.fill(dp,0);
for(int i = 1;i <= v;i++){
dp[i] = -1;
}
for(int i = 1;i <= n;i++){
for(int j = v;j >= arr[i][0];j--){
if(dp[j - arr[i][0]] != -1){
dp[j] = Math.max(dp[j],dp[j - arr[i][0]] + arr[i][1]);
}
}
}
System.out.println(dp[v] == -1 ? 0 : dp[v]);
}
}
完全背包
将01背包中的物品从1个变成无数个
完全背包第一问
1.状态表示
和01背包是一样的,这里不过多阐述
2.状态转移方程
这里分析思路也是像01背包一模一样
分析选不选i之后的多种情况,取其中的最大值,可以使用多一个for循环来判断,前提是j - nw[i]要大于等于0,然后再填表,这里直接给出数学证明
将dp[i][j]表示成以上形式
其他的细节判断与01背包问题一致(学明白01背包再来看完全背包是很简单的)
3.初始化
第一行可以很清楚的知道,因为从0个物品里选择总体积为j的最大价值,都为0
第一列,从i个物品里选择总体积为0的最大价值,可以让表自己填,因为会判断是否j - nw[i]要大于等于0
4.填表顺序
与01背包不同
从上到下,从左到右填表
5.返回值
直接返回dp[n][v]
n是前n个物品,v是背包体积
可以写出代码
完全背包第二问
1.状态表示
和01背包是一样的,这里不过多阐述
2.状态转移方程
与第一问一样,不同的是无法选出体积的需要标记成-1,此外还需要判断是否为-1
3.初始化
第一行可以很清楚的知道,除了第一个为0,其他的都选不到,全部填-1
第一列,从i个物品里选择总体积为0的最大价值,可以让表自己填,因为会判断是否j - nw[i]要大于等于0
4.填表顺序
与上一问一致
从上到下,从左到右填表
5.返回值
直接返回dp[n][v]
n是前n个物品,v是背包体积
可以写出代码
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param v int整型
* @param n int整型
* @param nums int整型ArrayList<ArrayList<>>
* @return int整型ArrayList
*/
public ArrayList<Integer> knapsack (int v, int n, ArrayList<ArrayList<Integer>> nums) {
// write code here
ArrayList<Integer> ret = new ArrayList<>();
int[][] dp = new int[n + 1][v + 1];
for(int i = 1;i <= n;i++){
for(int j = 0;j <= v;j++){
dp[i][j] = dp[i - 1][j];
if(j - nums.get(i - 1).get(0) >= 0){
dp[i][j] = Math.max(dp[i][j],dp[i][j - nums.get(i - 1).get(0)] + nums.get(i - 1).get(1));
}
}
}
ret.add(dp[n][v]);
for(int i = 1;i <= n;i++){
Arrays.fill(dp[i],0);
}
for(int i = 1;i <= v;i++){
dp[0][i] = -1;
}
for(int i = 1;i <= n;i++){
for(int j = 0;j <= v;j++){
dp[i][j] = dp[i - 1][j];
if(j - nums.get(i - 1).get(0) >= 0 && dp[i][j - nums.get(i - 1).get(0)] != -1){
dp[i][j] = Math.max(dp[i][j],dp[i][j - nums.get(i - 1).get(0)] + nums.get(i - 1).get(1));
}
}
}
ret.add(dp[n][v] == -1 ? 0 : dp[n][v]);
return ret;
}
}
空间优化
我们还是基于填表顺序,来进行滚动数组的空间优化
从左向右从上往下,填完第一行时只有第一行最上面的才有用,我们从左向右填表,从上往下即可变成滚动数组的形式
1.不要去解释改变滚动数组的状态
2.自己写完代码后去删一下,删除行数,还有改变一下遍历的头尾即可