本文已参与「新人创作礼」活动,一起开启掘金创作之路。
1 0-1背包
题目:有N件物品和一个容量为V的背包。第i建物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大(每件物品只能被使用一次)
根据题目,可以确定使用动态规划思路进行求解,定义一个数组,表示有件物品,背包容量是所能装入的物品的最大价值。
则有状态转移方程如下:
其中,每次选取都有两种选择:
- 装入背包(背包能放得下该物品):则问题就转化成前件物品装入容量为的背包的最大价值,则表示为,再加上当前物品的价值,
- 不装入背包:那么问题就转换成前件物品放入容量为的背包的最大价值,则
/**
*
* @param n 物体的数量
* @param w 背包所能放物体的最大重量
* @param wt 每件物体的重量
* @param val 每件物体的价值
* @return
*/
public int dpSolution(int n, int w, int[] wt, int[] val) {
int[][] dp = new int[n + 1][w + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= w; j++) {
if (j >= wt[i - 1])//背包所能承受的重量大于物体的重量,就放进去
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - wt[i - 1]] + val[i - 1]);
else
dp[i][j] = dp[i -1][j];
}
}
return dp[n][w];
}
0-1背包的空间优化
回顾 01 背包的基本解法的状态转移方程,如下:
dp[i][j] = dp[i - 1][j] 当前物品不选
dp[i - 1][j - v[i]] + w[i] 当前物品选,j - v[i] 要大于等于 0
空间复杂度为 ,状态转移中 i 这一维只跟 i - 1 有关系,因此 i 这一维用滚动数组至少可以将 N 行优化为 2 行。这本来是动态规划的基本操作,在线性动态规划中很常见。这里特别提空间优化是因为 01 背包的 i 这一维用 1 行就可以解决。
假设状态只有一行,即 dp[j] := 占用了 j 空间的情况下可以取到的最大价值, 在推第 i 行的时候,dp 数组中存的是第 i - 1 行的信息。
看状态的两个转移方向,第一个是 ,这刚好就是当前 数组在 位置保存的数据,因此不用动,比较麻烦的是另一个,就是 。这里要用到第 行的 ,但是如果按照正常的 从 到 推的话,计算 的时候, 保存的已经是第 行信息了。
因此这里需要转换一下,从大往小推,推到 时, 都已经是第行的信息了,但是它们对 的计算没有影响,有影响的 此时还是第 行的信息,可以满足转移方程的需要。因此当空间这一维的状态从大往小推的时候, 这一维状态可以优化到一维。
这就是 01 背包的终极形态了。
dp[j] = max(dp[j], dp[j - v[i]] + w[i]) // j 从大往小推
完整代码:
/**
*
* @param n 物体的数量
* @param w 背包所能放物体的最大重量
* @param wt 每件物体的重量
* @param val 每件物体的价值
* @return
*/
public int dpSolution2(int n, int w, int[] wt, int[] val){
int [] dp=new int[w+1];
for (int i = 1; i < n+1; i++) {
for (int j=w;j>0;j--) {
if (j>=wt[i-1])
dp[j] = Math.max(dp[j],dp[j-wt[i-1]]+val[i-1]);
}
}
return dp[w];
}
2 完全背包
题目:有N件物品和一个容量为V的背包。第i建物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大(每件物品可以被无限次使用)
与0-1背包不同的是,完全背包中的每个物品可以被无限次使用。
状态转移方程:
- 不装入背包:则问题转化成从前个物品所能用的容量是,则有
- 装入背包:在0-1背包中,当取第个物品时,可以知道前个物品所能用的最大容量是,完全背包中每个物品可以取无限次,所以如果取第个物品,那么前个物品所能用的容量还是,。
/**
*
* @param n 物体的数量
* @param w 背包所能放物体的最大重量
* @param wt 每件物体的重量
* @param val 每件物体的价值
* @return
*/
public int dpSolution(int n, int w, int[] wt, int[] val) {
int[][] dp = new int[n + 1][w + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= w; j++) {
if (j >= wt[i - 1])//背包所能承受的重量大于物体的重量,就放进去
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - wt[i - 1]] + val[i - 1]);
else
dp[i][j] = dp[i -1][j];
}
}
return dp[n][w];
}
完全背包的空间优化
考虑空间优化后的状态转移方程如下:在 01 背包中,由于推第 行的 时需要第 行的 ,因此忌讳在推导 时, 已经更新过了, 这是 01 背包不希望发生的事,解决的办法就是 从大往小推。
而上述 01 背包不希望发生的事正是完全背包希望发生的,即推导第 行的 时,用到第 行的 。而这仅需要把 01 背包中的从大往小推 改为从小往大推 即可实现。这就是完全背包的优化巧妙的地方。
作者:力扣 (LeetCode) 链接:leetcode-cn.com/leetbook/re… 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
/**
*
* @param n 物体的数量
* @param w 背包所能放物体的最大重量
* @param wt 每件物体的重量
* @param val 每件物体的价值
* @return
*/
public int dpSolution2(int n, int w, int[] wt, int[] val){
int [] dp=new int[w+1];
for (int i = 1; i < n+1; i++) {
for (int j = 1; j <= w; j++) { //从小往大推
if (j>=wt[i-1])
dp[j] = Math.max(dp[j],dp[j-wt[i-1]]+val[i-1]);
}
}
return dp[w];
}
3 多重背包问题
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大
思路 1 :将物品展开,全拆成 01
这与完全背包的朴素思路一致。与完全背包的情况一样,这种方法是正确的,但是时间复杂度较高。
多重背包的朴素算法与完全背包的朴素算法没有区别,而多重背包的优化相对较难,并且力扣上面没有多重背包的题目。下面仅对多重背包的优化做简单的介绍。
思路 2 :2 进制分解
这是对思路 1 的优化。
有这样一个事实:任意一个数 n,它一定可以用 1,2,4,8,... ,以及 到 之间的某个数表示。例如 13 以内的所有数都可以用 1,2,4,6 表示。所以对于物品 , 数量限制是, 可以将其分成若干物品,它们的价值和体积为:( ,),(,),...
然后对这些物品做 01 背包。这样 01 背包的物品数就比思路 1 少很多了。这可以理解为类似于倍增法的思想。倍增法超出了力扣的范围,感兴趣的话可以找相关的资料学习。
以上就是背包动态规划的基本内容。背包动态规划在力扣上题目不多,下一节整理了 8 道背包动态规划的练习题,通过这些题可以大致了解背包问题的一些经典问题和常见的问法。
作者:力扣 (LeetCode) 链接:leetcode-cn.com/leetbook/re… 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。