从二维数组到一维数组——探索01背包问题的动态规划优化

这篇具有很好参考价值的文章主要介绍了从二维数组到一维数组——探索01背包问题的动态规划优化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。



本文将继续上一篇博客爬楼梯之后继续讲解同样用到了动态规划的 01背包问题

在解决动态规划问题时,我们经常面临着空间复杂度的挑战。01背包问题是一个典型的例子,通常使用二维数组来表示状态转移,但这样会带来额外的空间开销。在本文中,我们将探讨如何通过优化空间复杂度,将01背包问题从二维数组降维到一维数组,以提高算法的效率和性能。

题目

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

01背包 重量 价值
物品0 1 15
物品1 3 20
物品2 4 30

前知

背包问题

01背包问题是一个经典的动态规划问题,通常用来解决如何在有限的背包容量下选择物品以获得最大价值的问题。问题的描述是:给定一组物品,每种物品都有自己的重量和价值,在限定的背包容量下,选择哪些物品放入背包可以使得背包内物品的总价值最大化,且不能超过背包的承重。背包问题有以下几种:
从二维数组到一维数组——探索01背包问题的动态规划优化,刷题笔记,动态规划,算法,leetcode,java

从二维数组到一维数组——探索01背包问题的动态规划优化,刷题笔记,动态规划,算法,leetcode,java


二维dp数组

一、思路

首先,我们使用动态规划来解决01背包问题。通常,我们会使用一个二维数组dp[i][j]来表示在前i个物品中,背包容量为j时的最大总价值。我们初始化dp数组为0,并通过状态转移方程来更新dp数组,最终得到dp[n][C]作为问题的解,其中n为物品个数,C为背包容量。

01背包 重量 价值
物品0 1 15
物品1 3 20
物品2 4 30

二、解题方法

动规五部曲

  1. 确定dp数组及下标i的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是dp[i][j] ,如下图所示:
    从二维数组到一维数组——探索01背包问题的动态规划优化,刷题笔记,动态规划,算法,leetcode,java

  2. 确定递推公式:dp[i][j]可以由两个方向推出,要么是放i,要么就不放i,递推公式为: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

  • 不放物品i时:里面的最大价值实际上是dp[i - 1][j],因为此时代表已经达到背包最大容量时的最大价值,和上一层状态相同,当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。
  • 放物品i时:现在里面已经放了i,最大价值所以肯定有 value[i])dp[i - 1][j - weight[i]]代表的是放物品i并且剩余空间能够刚好放下物品i时的最大价值
  1. dp数组如何初始化:如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包里面都放不下物品,背包价值总和一定为0,如图:从二维数组到一维数组——探索01背包问题的动态规划优化,刷题笔记,动态规划,算法,leetcode,java
    再由递推公式可以看出dp[i][j]一定是由dp[i-1]得出的,所以还需要初始化最上面一层的dp数组,也就是dp[0][j]:存放编号0的物品的时候,各个容量的背包所能存放的最大价值。当最小的背包容量j连一个物品都存不下的时候,那么就让dp[0][j]=0,例如题目中把物品0的重量改为2,dp[0][1]就为0。如果背包能放得下物品的时候,dp[0][j] 应该是value[0],并且都是所有容量的都为value[0],因为01背包问题,物品只能取一次,如图:
    从二维数组到一维数组——探索01背包问题的动态规划优化,刷题笔记,动态规划,算法,leetcode,java
for (int j = 0 ; j < weight[0]; j++) {  // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。
    dp[0][j] = 0;
}
// 正序遍历
for (int j = weight[0]; j <= bagweight; j++) {
    dp[0][j] = value[0];
}

其它非0下标的dp数组都可以初始化为0,因为都是由上面一层或左上方推出来的,0会被覆盖掉
从二维数组到一维数组——探索01背包问题的动态规划优化,刷题笔记,动态规划,算法,leetcode,java

  1. 确定遍历顺序:从递推公式 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);可以看出,有两个for循环遍历方向得到dp数组,一个是物品,另一个是背包容量,由于dp[i][j]所需要的数据就是左上角,所以先遍历物品还是先遍历背包容量都没什么太大问题

先遍历物品,再遍历背包容量,一行一行遍历:
从二维数组到一维数组——探索01背包问题的动态规划优化,刷题笔记,动态规划,算法,leetcode,java
先遍历背包容量,再遍历物品,一列一列遍历:
从二维数组到一维数组——探索01背包问题的动态规划优化,刷题笔记,动态规划,算法,leetcode,java

  1. 举例推导dp数组:最终结果dp[2][4]
    从二维数组到一维数组——探索01背包问题的动态规划优化,刷题笔记,动态规划,算法,leetcode,java

三、Code

public class BagProblem {
    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");
        }
    }
}

一维dp数组

一、思路

使用二维数组会带来额外的空间开销。为了优化空间复杂度,我们可以通过滚动数组将二维数组降维为一维数组。我们定义一个一维数组dp[j],其中dp[j]表示背包容量为j时的最大总价值。然后,我们先从前往后遍历物品,再通过逆序遍历背包容量dp数组来更新状态,最终得到dp[C]作为问题的解。这样,我们成功将空间复杂度从O(n*C)降低到了O(C),大大提高了算法的效率和性能。

01背包 重量 价值
物品0 1 15
物品1 3 20
物品2 4 30

二、解题方法

把dp[i - 1]那一层拷贝到dp[i]上,对状态进行压缩,只用一个dp[j]滚动数组

动规五部曲

  1. 确定dp数组及下标i的含义:容量为j的背包,所背的物品价值可以最大为dp[j]

  2. 确定递推公式:一维dp数组相对于二维dp数组的写法,就是把dp[i][j]中i的维度去掉了。dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i,所以递推公式为dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

  3. dp数组如何初始化:当背包容量为0时,所背的物品价值dp[0]最大就是0,dp数组非0下标都让值为1,这样在递推之后,取到的最大值就会是递推过程中的最大值,而不会被其它初始值给覆盖掉

  4. 确定遍历顺序:根据递推公式得出同样有两个方向,首先从前到后遍历物品,再倒序遍历背包容量,这个顺序不能像二维dp数组一样调换顺序。

  • 如果是先倒序遍历背包容量,再正序遍历物品的话,那么里面的for循环会重复从第一个物品开始遍历,就只会在背包里添加一个物品。
  • 如果是先从前到后遍历物品,再正序遍历背包容量的话,物品0就不止添加了一次,而会被重复添加,从后向前遍历的话,前面的状态就不会被后面所调用到。
    正序:dp[1] = dp[1 - weight[0]] + value[0] = 15
    dp[2] = dp[2 - weight[0]] + value[0] = 30
    倒序:dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)
    dp[1] = dp[1 - weight[0]] + value[0] = 15
  • 二维dp数组遍历的时候不用倒序是因为对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}
  1. 举例推导dp数组:用题目进行举例
    从二维数组到一维数组——探索01背包问题的动态规划优化,刷题笔记,动态规划,算法,leetcode,java

三、Code

    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] + " ");
        }
    }

总结

通过这篇博客,读者可以清晰地了解如何通过优化空间复杂度,将01背包问题的动态规划解法从二维数组降维到一维数组,并且可以对比二者在性能上的差异,从而更好地掌握这一知识点。希望本文能够帮助读者更好地理解和应用动态规划算法在01背包问题中的使用,如果有任何疑问或者建议,欢迎留言讨论🌹文章来源地址https://www.toymoban.com/news/detail-847687.html

到了这里,关于从二维数组到一维数组——探索01背包问题的动态规划优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【动态规划】01背包问题(滚动数组 + 手画图解)

            01背包除了可以用形象的二维动态数组表示外,还可以使用空间复杂度更低的一维滚动数组。 目录 文章目录 前言 一、滚动数组的基本理解 二、确定dp及其下标含义 三、确定递推公式 四、确定初始化 五、确定遍历顺序 1.用物品(正序)遍历背包(正序) 实现代码:

    2023年04月08日
    浏览(24)
  • 算法训练第四十二天|01背包问题 二维 、01背包问题 一维、416. 分割等和子集

    视频链接:https://www.bilibili.com/video/BV1cg411g7Y6/ 参考: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 对于面试的话,其实掌握01背包,和完全背包,就够用了,最多可以再来一个多重背包。 而完全背包又是也是01背包稍作变化而来,即:完全

    2024年02月01日
    浏览(32)
  • 第九章 动态规划part04(● 01背包问题,你该了解这些! ● 01背包问题,你该了解这些! 滚动数组 ● 416. 分割等和子集 )

    ● 01背包问题,你该了解这些! ● 01背包问题,你该了解这些! 滚动数组 ● 416. 分割等和子集 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 1.确定dp数组以及下标的含义 i是物品,j是背包容量

    2024年01月16日
    浏览(34)
  • Day42|动态规划part04: 01背包问题,你该了解这些!、滚动数组、416. 分割等和子集

    其他背包,面试几乎不会问,都是竞赛级别的了,leetcode上连多重背包的题目都没有,所以题库也告诉我们,01背包和完全背包就够用了。 而完全背包又是也是01背包稍作变化而来,即:完全背包的物品数量是无限的。 01 背包问题描述 有n件物品和一个最多能背重量为w 的背包

    2024年04月25日
    浏览(29)
  • 动态规划|01背包理论基础(滚动数组)

    卡码网第46题 (opens new window) 现在差不多搞明白01背包问题了   昨天动态规划:关于01背包问题,你该了解这些! (opens new window)中是用二维dp数组来讲解01背包。 今天我们就来说一说滚动数组,其实在前面的题目中我们已经用到过滚动数组了,就是把二维dp降为一维dp,一些录

    2024年04月26日
    浏览(29)
  • 动态规划(DP)---- 01背包入门详解----二维图是学会的关键

        动态规划,Dynamic Programing(简称DP),个人认为是一种 算法思想 , 用来解决多阶段多层次的选择问题,把一个复杂的问题分解成每个小块的子问题然后一个个解决来找到最优解。     DP适用 重叠子问题 和 最优子结构的性质 的问题。     DP问题范围分为 线性与非线性

    2024年02月03日
    浏览(27)
  • 0-1背包问题思路分析,重点解释一维dp数组的01背包问题为什么要倒序遍历背包,以及为什么不能先遍历背包,只能先遍历物品

    对0-1背包问题的二维dp数组以及一维dp数组的思路分析 来源:代码随想录 link 本文是我对01背包问题的理解 ,在本文中具体分析dp数组的形成过程,最核心的地方就是我对每种情况下的01背包问题给出了代码运行结果,便于读者理解。 重点解释了为什么一维dp数组的01背包问题

    2024年02月03日
    浏览(74)
  • 从01背包开始动态规划:暴力解法 + dp + 滚动数组 + dp优化

        01背包问题是动态规划中最经典的问题之一,本篇将通过给出其四种解法,使读者更深刻理解动态规划。   有N件物品和一个容量是 V 的背包,每个物品有各自的体积和价值,且每个物品只能放一次(这也是01背包名字的由来),如何让背包里装入的物品具有最大的价值总

    2024年04月17日
    浏览(40)
  • 【动态规划专栏】-- 01 背包问题 -- 动态规划经典题型

    目录 背包问题概述 01 背包问题 01背包⭐⭐  【算法原理】 第一问 第二问 C++ 算法代码 复杂度分析 【空间优化 - 滚动数组】 C++ 算法代码 复杂度分析 分割等和子集⭐⭐ 【算法原理】  对于类01背包问题 C++ 算法代码  【空间优化 - 滚动数组】  C++ 算法代码 目标和⭐⭐ 【算

    2024年02月05日
    浏览(41)
  • 动态规划——01背包问题

    由于本人实力尚浅,接触算法没多久,写这篇blog仅仅是想要提升自己对算法的理解,如果各位读者发现什么错误,恳请指正,希望和大家一起进步。(●’◡’●) 状态表示 :用一个数组 f[][] (数组可能是一维也可能是二维,根据具体题目具体分析)来表示某个集合,这个集合

    2024年02月03日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包