[日记]LeetCode算法·十七——动态规划② 01背包问题

文章介绍了01背包问题,这是动态规划中的经典问题。通过解释暴力解法和动态规划方法,重点讨论了如何使用二维和一维动态规划数组来优化解决方案,以找到装入背包中使物品价值总和最大的物品组合。并给出了相应的代码示例,展示了一维DP数组如何节省空间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 01背包问题

背包问题是动态规划的经典问题,而其中01背包又是基础中的基础。
01背包问题:
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

从暴力接发的思路出发,每一个物品都有不选两种情况,利用回溯法可以进行暴力遍历,除去重量超标的剪枝,基本上其时间复杂度为O(2^N)。
因此,采用动态规划解决01背包问题无疑是更好的思路。

2 二维dp数组(思路清晰)

首先让我们确定好dp数组的含义,从而理解递推公式。

  • dp[i][j]代表着从[0,1,2,…,i]选取物品放入容量为j的背包所能产生的最大价值。

接着让我们考虑递推公式,对于dp[i][j]而言,存在着两种可能:

  • 不选物体i,即选了后可能超标的情况下,那么显然dp[i][j]=dp[i-1][j],其所能容纳最大价值依然从[0,…,i-1]中挑选。
  • 选物体i,那么我们需要保证背包不会超重,那么dp[i][j]=dp[i][j-weight[i]+value[i]],其中j-weight[i]为在放入物体i前背包所能容纳的最大重量。

综上所述,dp[i][j] = j < weight[i] ? dp[i-1][j] : max( dp[i-1][j] ,dp[i-1][ j - wight[i] ] + value[i] )
最后我们考虑初始化的问题,显然dp[i][j]的递推公式,由左上角得到,所以,我们理应初始化第一行第一列

  • 对于第一列而言,因为j=0,,背包容纳不下任何物体,显然dp[i][0]=0;
  • 对于第一行而言,因为只考虑装不装物体0,所以,j>=weight[0]时,dp[0][j]=value[0],其余情况依然dp[0][j]=0。

综上,我们已经得到了使用二维数组01背包问题的方案,具体的代码如下:

int bagProblem(vector<int>& weight, vector<int>& value, int bagWeight)
{
    //先全部初始化为0,省略第一列的初始化
    vector<vector<int>> dp(weight.size(),vector(bagWeight+1,0));
    //初始化第一行,从weight[0]开始初始化,背包容量自然地能容纳物体0
    for(int j=weight[0]; j < bagWeight+1; ++j)
    {
        dp[0][j]=value[0];
    }
    //递推公式
    for(int i=1; i < dp.size(); ++i)
    {
        for(int j=1; j<=bagWeight; ++j)
        {
            //超重,放不下物体i
            if(weight[i] > j)
                dp[i][j]=dp[i-1][j];
            else
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
        }
    }
    //返回结果
    return dp[dp.size()-1][bagWeight];
}

3 一维dp数组

观察上述二维dp数组的递推公式:

  • dp[i][j] = j < weight[i] ? dp[i-1][j] : max( dp[i-1][j] ,dp[i-1][ j - wight[i] ] + value[i] )

可以看到,自始至终,我们都只是用了dp[i-1],只利用了上一行的结果,而没有使用更早的dp[i-2],dp[i-3]的信息,这意味着我们可以只使用一维dp数组进行覆盖,从节省空间。
从这个思路出发,由于递推公式时从之前二维数组的左上角递推得到,我们为了使用上一行的结果,这一次遍历必然是从大到小,从右到左进行遍历,才能防止信息覆盖。
甚至在初始化阶段,因为全部初始化为0,且是从大到小进行遍历,因此甚至不需要进行条件判断,直接进行取最大值即可。

int bagProblem(vector<int>& weight, vector<int>& value, int bagWeight)
{
    //初始化已经完成,因为
    vector<vector<int>> dp(bagWeight+1,0);
    //遍历顺序为先遍历物体,再遍历背包
    //即先测试前i+1个物体放入各种容量的背包的可能性,直到所有物体的可能性都被测试
    //不能先遍历背包,先遍历背包意味着对容积为最大几个的背包,测试了都只放入1个物体时的结果。
    for(int i=0; i < weight.size(); ++i)
    {
        //递推公式,j>=weight[i]
        for(int j=bagWeight; j>=weight[i]; --j)
        {
            //因为初始化为0,所以最开始必然会初始化为value[j]
            dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
        }
    }
    //返回结果
    return dp[dp.size()-1];
}

4 总结

赶作业去了,今天没怎么刷题,只做了个Leetcode的每日一题,保证下出勤率。
唉,又开始上这些不知所谓的课程,开始占用我的时间,争取明天好好刷动态规划的Leetcode。
——2023.3.2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值