动态规划-01背包模板题

01背包(NowCoder DP41)

题目描述:
在这里插入图片描述
在这里插入图片描述

状态表示:
看题目描述我们可以发现这题分为两个问题,第一题的意思是背包可以不用装满,求此时的最大价值,所以我们建立二维数组f,使用f[i][j]来表示在前i个物品之中选择并且装入背包体积总和小于等于j,所有选法中能够在背包装入的最大价值。第二题的意思是你要将背包装满时的最大价值,如果没装满那么价值相当于0,也是一样建立二维数组g[i][j]表示在前i个物品中间选择之后并且装入背包体积总和等于j时,所以选法中的最大价值。另外给小白说一下就是这题为什么叫01背包,因为每种物品只有一个,只有0和1这种状态,如果每种物品的数量是无数个,那么这种问题就叫做完全背包问题。
状态转移方程:
根据我们解决动态规划问题的经验,我们对处理的尾部进行考虑。比如第一问,我们可以分为将第i个物品放入背包和将第i个物品不放入背包两种情况,在第一种情况下,因为我们都没有将第i个物品放入背包i,所以体积以及价值都没有变化,f[i][j]=f[i-1][j],第二种情况就是将第i个物品放入背包,此时价值增大,占用背包体积增大,但是要确定背包容量还装的下这个物品,就是要保证背包此时体积减去物品体积即j-volume[i]要大于等于0,此时f[i][j]=value[i]+f[i-1][j-volume[i]],比较两种情况得到f[i][j]的最大值;对于第二问,稍微与第一问有点区别。首先在第二题中可能会出现无法装满背包的情况,此时就直接给g数组对应的值赋为-1。第二题也分为两种情况,第一种情况将第i个物品不放入背包,此时g[i][j]=g[i-1][j],当g[i-1][j]放不满背包时,显然此时g[i][j]也是放不满的,所以这里无需判断条件,直接赋值即可。第二种情况就是将第i个物品放入背包时的情况,判断条件除了j-volume[i]要大于等于0,此时还要判断g[i-1][j-volume[i]]是否等于-1,因为后面的运算是要将g[i-1][j-volume[i]]加上value[i],会让-1变为正数影响逻辑结果,最后将这两种情况比较最大值。
初始化:
对于f和g数组都是加上第0行和第0列,第0行就是表示不加入物品,第0列就表示表示背包空间为0。因为针对第一题的f背包无需装满,所以对于f数组的第0行和第0列赋为0即可。但是第二题的g数组有不同,第0列一样全赋为0,第0行从第1列开始全赋为-1,因为此时背包空间不为0,但是物品体积为0,物品永远填不满背包,所以都赋为-1。
填表顺序:
总体上都是从上至下。
返回值:
输入的物品个数为n,背包体积为v,那么第一题和第二题返回值分别为f[n][v]和g[n][v]。但是第二题返回值要注意处理细节就是当返回值为-1时要返回0,因为-1是在逻辑上帮我们判断物品填不满背包的,此时的填满背包的最大价值就是0。
代码如下:

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int n = in.nextInt();
            int v = in.nextInt();

            int[][] things = new int[n][2];
            for (int i = 0; i < n; i++) {
                int vol = in.nextInt();
                int val = in.nextInt();

                things[i][0] = vol;
                things[i][1] = val;
            }


            int[][] f = new int[n + 1][v + 1];
            int[][] g = new int[n + 1][v + 1];

            for (int i = 1; i <= v; i++) {
                g[0][i] = -1;
            }

            for (int i = 1; i <= n; i++) {

                for (int j = 1; j <= v; j++) {
                    f[i][j] = f[i - 1][j];
                    if (j - things[i - 1][0] >= 0) {
                        f[i][j] = Math.max(f[i][j],
                                           f[i - 1][j - things[i - 1][0]] + things[i - 1 ][1]);
                    }
                    g[i][j] = g[i - 1][j];
                    if (j - things[i - 1][0] >= 0 && g[i - 1][j - things[i - 1][0]] != -1 ) {
                        g[i][j] = Math.max(g[i][j],
                                           g[i - 1][j - things[i - 1][0]] + things[i - 1 ][1]);
                    }
                }
            }

            System.out.println(f[n][v]);
            System.out.println(g[n][v] == -1 ? 0 : g[n][v]);
        }
    }
}

优化:
对于01背包问题我们对于其空间的优化就是采用滚动数组的方式。通过以上代码我们不难发现,状态转移方程都有一个特点,影响最终的f[i][j]以及g[i][j]取值的只有其上一行的数据,因此我们自然会想到只使用两个一维数组来记录状态转移时的数据。先初始化第一个数组,然后第二个数组以第一个数组为基础来计算其中的值,之后再将第二个数组视为逻辑上的第一个数组,第一个数组视为逻辑上的第二个数组,更新第一个数组的值,就这样交替更新,直到得到最后的值。
但是我们在这里直接使用一个数组来进行操作,照样能够完成这种滚动数组的效果。如果不能理解使用单个数组什么原因也没有太大关系,不需要强行去解释,这题起到一个模板的作用,多用多做应该就能融会贯通。
优化代码如下:

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int n = in.nextInt();
            int v = in.nextInt();

            int[][] things = new int[n][2];
            for (int i = 0; i < n; i++) {
                int vol = in.nextInt();
                int val = in.nextInt();

                things[i][0] = vol;
                things[i][1] = val;
            }


            int[] f = new int[v + 1];
            int[] g = new int[v + 1];

            for (int i = 1; i <= v; i++) {
                g[i] = -1;
            }

            for (int i = 1; i <= n; i++) {

                for (int j = v; j >= things[i - 1][0]; j--) {
                
                        f[j] = Math.max(f[j],
                                        f[j - things[i - 1][0]] + things[i - 1 ][1]);
                    

                    if (g[j - things[i - 1][0]] != -1 ) {
                        g[j] = Math.max(g[j],
                                        g[j - things[i - 1][0]] + things[i - 1 ][1]);
                    }
                }
            }

            System.out.println(f[v]);
            System.out.println(g[v] == -1 ? 0 : g[v]);
        }
    }
}

题目链接
时间复杂度:O(N^2)
优化前空间复杂度:O(N^2)
优化后空间复杂度:O(N)

  • 33
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值