问题描述
给你一个可装载重量为 W
的背包和 N
个物品,每个物品有重量和价值两个属性。其中第 i
个物品的重量为 wt[i]
,价值为 val[i]
,现在让你用这个背包装物品,最多能装的价值是多少?
解题思路
01背包问题是一个非常经典的动态规划问题,可以很好的入门动态规划,与之相关的变形题目也是非常的多
(1)状态和选择是什么?
- 状态:由于是不断将物品放入状态,所以变化的量有两个——“背包容量”和可选择的物品
- 选择:选择就是要么装进去要么不装进(也就是0和1)
因此选择导致了状态变化,如果选择装入将导致背包容量减少以及可选择的物品减少
(2)dp数组如何定义
接着要根据状态和选择来定义dp数组。dp数组完成的就是动态转移,dp数组的值一定是价值,这是毋庸置疑的。动态规划框架如下
题目问的是当给定物品个数为N,背包容量为W时所能装的最大的价值数目,那么我们将dp数组也定义为dp[i][w]
(动态规划本质就在于状态的转移,就像是砌墙一样,上面的每一砖每一瓦都代表一个状态,它们的到的和下面的砖瓦是息息相关的),它表示对于前i个物品,如果给定容量为w,可以装的最大价值为dp[i][w]
后序的操作就是从最简单的情形开始,一步一步推导每个状态,最终当返回i=N
,w=W
时的结果即可。
(3)base case
base case很容易想到。最极端最简单的情况自然是i=0,此时表示不选择物品,以及w=0,此时表示背包容量为0。他们的结果均为0
(4)框架
于是完善最原始的框架如下
int dp[N+1][W+1] dp[0][..] = 0 dp[..][0] = 0 for i in [1..N]: for w in [1..W]: dp[i][w] = max( 把物品 i 装进背包, 不把物品 i 装进背包 ) return dp[N][W]
动态规划最难的地方,就是动态转移,也即是如何将上面的文字表述为代码
(5)动态转移
- 如果没有把第
i
个物品放在背包:很显然,既然没有把第i个放进去,那么价值量不会增加,状态也不会变化,也即dp[i][w]==dp[i-1][w]
- 如果把第
i
个物品放入了背包:既然放入了背包,那么此状态的容量一定会减少wt[i]
,而价值则会增加val[i]
,因此dp[i][w]==dp[i-1][w-wt[i-1]]+val[i-1]
需要注意的是i
是从1开始的,因此val
和wt
的索引中i-1
表示第i
个物品。所以dp[i][w]==dp[i-1][w-wt[i-1]]+val[i-1]
表示如果把第i个物品装入了,就要寻找剩余重量w-wt[i-1]
限制下的最大价值,加上第i
个物品的价值val[i-1]
代码
int knapsack(int W, int N, vector<int>& wt, vector<int>& val) { // vector 全填入 0,base case 已初始化 vector<vector<int>> dp(N + 1, vector<int>(W + 1, 0)); for (int i = 1; i <= N; i++) { for (int w = 1; w <= W; w++) { if (w - wt[i-1] < 0) { // 当前背包容量装不下,只能选择不装入背包 dp[i][w] = dp[i - 1][w]; } else { // 装入或者不装入背包,择优 dp[i][w] = max(dp[i - 1][w - wt[i-1]] + val[i-1], dp[i - 1][w]); } } } return dp[N][W]; }
01背包问题相关题目
1:牛客-求正数数组的最小不可组成和
如果按照原生的背包问题可以这样理解:min
为最轻物品的质量,sum
为所有物品的总质量,假设有一个背包,其容量范围在[min,sum]
之间,还有len
件不同重量的物品、、、
也即把数组中的数据看作物品的重量,如果这些物品不能填满某个容量(范围为[min,max])的背包,就表示不能组成那个范围的数
class Solution { public: /** * 正数数组中的最小不可组成和 * 输入:正数数组arr * 返回:正数数组中的最小不可组成和 */ int getFirstUnFormedNum(vector<int> arr, int len) { //范围为[min,sum]; int sum=0,min=arr[0]; int i,j; for(int i=0;i<len;i++) { sum+=arr[i]; min=arr[i] < min ? arr[i] : min; } vector<int> dp(sum+1,0); for(i=0;i<len;i++) { for(j=sum;j>=arr[i];j--)//对于背包容量小于物品的直接忽略 { if(dp[j] < dp[j-arr[i]]+arr[i])//选上了 dp[j]=dp[j-arr[i]]+arr[i]; else//没选上 dp[j]=dp[j]; } } //最后只要放入的重量不是那个区间的数肯定就是所求 for(i=min;i<=sum;i++) { if(i!=dp[i]) return i; } return sum+1; } };