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