最近看了很多背包问题相关的算法,现对其总结如下。
几种常见的类型:
-
0/1背包问题(特点:对第i件物品的两种选择,放或不放)
题目:
有N件物品和一个容量为V 的背包。放入第i件物品耗费的费用是Ci,得到的价值是Wi。求解将哪些物品装入背包可使价值总和最大。
基本思路:
空间优化:
-
完全背包问题
题目:
基本思路:
解决方法:转换为0/1背包问题
最简单的想法是,考虑到第i种物品最多选⌊V /Ci⌋件,于是可以把第i种物品转化为⌊V /Ci⌋件费用及价值均不变的物品,然后求解这个01背包问题。
这两种背包问题是面试和笔试经常遇到的问题。
题目汇总:
1.lintcode 92 -背包问题
题目描述:
在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i]
样例
样例 1:
输入: [3,4,8,5], backpack size=10
输出: 9
样例 2:
输入: [2,3,5,7], backpack size=12
输出: 12
挑战
O(n x m) 的时间复杂度 and O(m) 空间复杂度
如果不知道如何优化空间O(n x m) 的空间复杂度也可以通过.
注意事项
你不可以将物品进行切割。
实现1:二维DP
public class Solution {
/**
* @param m: An integer m denotes the size of a backpack
* @param A: Given n items with size A[i]
* @return: The maximum size
*/
public int backPack(int m, int[] A) {
// write your code here
//dp[i][j]表示用前i种物品装到容量为j的背包所能装的最大重量
int[][] dp=new int[A.length][m+1];
for(int j=0;j<=m;j++){
if(j>A[0]){
dp[0][j]=A[0];
}
}
for(int i=1;i<A.length;i++){
for(int j=1;j<=m;j++){
if(j>=A[i]){
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-A[i]]+A[i]);
}else{
dp[i][j]=dp[i-1][j];
}
}
}
return dp[A.length-1][m];
}
}
实现2:优化为一维DP
解法1:i从1开始,i对应的维度是A.length+1,对应的第一行值全为0;
public int backPack(int m, int[] A) {
// write your code here
int[] dp=new int[m+1];
for(int i=1;i<=A.length;i++){
for(int j=m;j>=A[i-1];j--){
dp[j]=Math.max(dp[j],dp[j-A[i-1]]+A[i-1]);
}
}
return dp[m];
}
解法2:i从0开始,对应的维度是A.length,第一行是A[0]=3时对应的行(以第一个测试用例为例)
public int backPack(int m, int[] A) {
// write your code here
int[] dp=new int[m+1];
for(int i=0;i<A.length;i++){
for(int j=m;j>=A[i];j--){
dp[j]=Math.max(dp[j],dp[j-A[i]]+A[i]);
}
}
return dp[m];
}
2.lintcode 125 背包II
题目描述:
有 n
个物品和一个大小为 m
的背包. 给定数组 A
表示每个物品的大小和数组 V
表示每个物品的价值.
问最多能装入背包的总价值是多大?
思路:
设定 dp[i][j] 表示前 i 个物品装入大小为 j 的背包里, 可以获取的最大价值总和. 决策就是第i个物品装不装入背包, 所以状态转移方程就是 dp[i][j] = max(dp[i - 1][j], f[i - 1][j - A[i]] + V[i])
可以使用滚动数组优化空间至 O(m).
实现1:二维DP
public int backPackII(int m, int[] A, int V[]) {
// write your code here
int[][] dp = new int[A.length + 1][m + 1];
for (int i = 0; i <= A.length; i++) {
for (int j = 0; j <= m; j++) {
if (i == 0 || j == 0) {
dp[i][j] = 0;
} else if (j<A[i - 1]) {
dp[i][j] = dp[(i - 1)][j];
} else {
dp[i][j] = Math.max(dp[(i - 1)][j], dp[(i - 1)][j - A[i - 1]] + V[i - 1]);
}
}
}
return dp[A.length][m];
}
实现2:优化为一维
public int backPackII(int m, int[] A, int V[]) {
// write your code here
int[] dp=new int[m+1];
for(int i=0;i<A.length;i++){
for(int j=m;j>=1;j--){
if(j>=A[i]){
dp[j]=Math.max(dp[j],dp[j-A[i]]+V[i]);
}
}
}
return dp[m];
}
3.lintcode 440 完全背包问题
划重点:each item has an infinite number available 每个物品都可以无限次使用,刚好对应到上面的完全背包问题。
基本思路跟上题一样,只不过由于可以重复,所以在最内层循环还要再加一层while循环用于遍历枚举重复的组合。时间复杂度是O(m * n * k)。
状态转移方程
dp[i][j] 表示前 i 个物品装入大小为 j 的背包里, 可以获取的最大价值总和
dp[ 0 ][ j ] = 0 ;
dp[ i + 1 ][ j ] = max (dp[i][j - k * w[ i ] ] + k * v[ i ] ) (0 <= k * w[ i ] <= j )
上述关系写出的代码:
for(int i=0; i<n; i++)
for(int j=0; j<=W; j++)
for(int k=0; k*w[i]<=j; k++)
dp[i+1][j]=max(dp[i+1][j],dp[i][j-k*w[i]]+k*v[i]);
printf("%d\n",dp[n][W]);
三重循环,时间复杂度太高。
实现1:二维数组
for(int i=0; i<n; i++)
for(int j=0; j<=W; j++)
if(j<w[i])
dp[i+1][j]=dp[i][j];
else
dp[i+1][j]=max(dp[i][j],dp[i+1][j-w[i]]+v[i]);
printf("%d\n",dp[n][W]);
优化:一位数组
int dp[MAXN];
for(int i=0; i<n; i++)
for(int j=w[i]; j<=W; j++)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
printf("%d\n",dp[W]);
参考:
https://blog.csdn.net/luoshengkim/article/details/76514558
https://blog.csdn.net/a_b_c_d_e______/article/details/81512036