【动态规划】详解背包问题之二维数组转一维

背包问题的分类

今天题目的难点在于如何理解将二维dp数组转换成一维的dp数组,我们先看下题目:

题目描述

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。

小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

输入描述

第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。

第二行包含 M 个正整数,代表每种研究材料的所占空间。

第三行包含 M 个正整数,代表每种研究材料的价值。

输出描述

输出一个整数,代表小明能够携带的研究材料的最大价值。

输入示例

复制代码
6 1
2 2 3 1 5 2
2 3 1 5 4 3

输出示例

复制代码
5

提示信息

小明能够携带 6 种研究材料,但是行李空间只有 1,而占用空间为 1 的研究材料价值为 5,所以最终答案输出 5。

数据范围:

1 <= N <= 1000

1 <= M <= 1000

研究材料占用空间和价值都小于等于 1000

按照卡哥的动规五部曲思路我们进行如下分析(感谢卡哥)

  • 第一步:确定dp[i][j]的具体含义:表示从下标为[0 - i-1]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
  • 第二步:初始化 dp 数组做了简化(给物品增加冗余维)。这样初始化dp数组,默认全为0即可。这一步其实就是模仿背包重量从 0 开始,背包容量 j 为 0 的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为 0。可选物品也可以从无开始,也就是没有物品可选,即dp[0][j],这样无论背包容量为多少,背包价值总和一定为 0。
  • 第三步:确定递推公式
    • 当前背包的容量都没有当前物品i大的时候,是不放物品i的,那么前i-1个物品能放下的最大价值就是当前情况的最大价值
    • 当前背包的容量可以放下物品i,那么此时分两种情况:
      • 1、不放物品i
      • 2、放物品i
    • 比较这两种情况下,哪种背包中物品的最大价值最大
  • 第四步:确定遍历顺序
  • 第五步:打印dp数组
java 复制代码
/**
 * @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
                dp[i][j] = dp[i - 1][j];
            } else {
                /**
                 * 
                 */
                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));
    }
}

那么如何将二维数组优化成一维的? 我们看下代码:

java 复制代码
private static void testWeightBagProblem_3(int[] weight, int[] value, int bagSize) {
    int[] dp = new int[bagSize + 1];
    dp[0] = 0;
    for (int i = 1; i <= bagSize; i++) {
        dp[i] = value[0];
    }
    for (int j = 1; j < value.length; j++) {
        for (int i = bagSize; i >= weight[j]; i--) {
            dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);
        }
    }

}

注意到了吗?跟二维的有什么区别?

倒叙遍历! 为什么要倒叙遍历?

由于一位数组是根据二维优化而来的,因此如果仍然从前向后遍历,遍历到后面的时候前面的元素可能已经被修改过了,此时dp[i - weight[j]]再获取到的就不是上一行的值了,因此需要倒叙遍历,以确保dp[i - weight[j]]获取到的仍是上一行的值。

相关推荐
devlei7 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
努力的小郑9 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Victor3569 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3569 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁10 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp10 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴11 小时前
COBOL语言的云计算
开发语言·后端·golang
普通网友11 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒12 小时前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端
Soofjan13 小时前
Go 内存回收-GC 源码1-触发与阶段
后端