01背包问题
给定一个容量为C的背包,重量为weight,价值为value的石头,求背包能容纳的最大价值。
例
weight = [1, 2, 3]
value = [6, 10 ,12]
C = 5
输出:22
背包中放入一个重量为3的石头和一个重量为2的石头
value = 10 + 12 = 22.
递归
public int findRes(int[] weight, int[] value, int capcity) {
if(weight.length != value.length) return 0;
if(capcity < 0)return 0;
if(weight.length == 0)return 0;
return helper(weight, value, capcity, weight.length - 1);
}
//f(n, c) 考虑[0 - n] 中背包重量为c的最优解
//状态转移方程为 f(n, c) = Math.max(不考虑最后一个石头, 最后一个石头的价值 + 考虑最后一个石头后剩余背包容量的最大值);
//状态转移方程为 f(n, c) = Math.max(f(n - 1, c), v[n] + f(n - 1, c - w[n]));
private int helper(int[] weight, int[] value, int capcity, int index) {
//如果背包中
if(index < 0 || capcity <= 0)return 0;
int res = helper(weight, value, capcity, index - 1);
if(capcity >= weight[index])
res = Math.max(res, value[index] + helper(weight, value, capcity - weight[index], index - 1));
return res;
}
不难发现此种解法会有重叠计算的问题
记忆化搜索-自下而上
我们开辟一个空间存储之前计算的结果
public int findRes2(int[] weight, int[] value, int capcity) {
if(weight.length != value.length) return 0;
if(capcity < 0)return 0;
if(weight.length == 0)return 0;
//行:是否考虑该位置的石头 0:只考虑第一个 1:考虑1 n:考虑n
//列:背包剩余容量
int memo[][] = new int[weight.length][capcity + 1];
for(int i = 0; i < memo.length; i ++)
Arrays.fill(memo[i], - 1);
return helper2(weight, value, capcity, weight.length - 1, memo);
}
//f(n, c) 考虑[0 - n] 中背包重量为c的最优解
//状态转移方程为 f(n, c) = Math.max(不考虑最后一个石头, 最后一个石头的价值 + 考虑最后一个石头后剩余背包容量的最大值);
//状态转移方程为 f(n, c) = Math.max(f(n - 1, c), v[n] + f(n - 1, c - w[n]));
private int helper2(int[] weight, int[] value, int capcity, int index, int[][] memo) {
//如果背包中
if(index < 0 || capcity <= 0)return 0;
if(memo[index][capcity] != -1)return memo[index][capcity];
int res = helper2(weight, value, capcity, index - 1, memo);
if(capcity >= weight[index])
res = Math.max(res, value[index] + helper2(weight, value, capcity - weight[index], index - 1, memo));
memo[index][capcity] = res;
return res;
}
动态规划-自上而下
//时间复杂度为O(C * n) 其中C为背包容量,n为石头个数
//空间复杂度为O(C * n) 其中C为背包容量,n为石头个数
private int findRes3(int[] weight, int[] value, int capcity) {
if(weight.length != value.length) return 0;
if(capcity <= 0)return 0;
if(weight.length == 0)return 0;
//行:是否考虑该位置的石头 0:只考虑第一个 1:考虑1 n:考虑n
//列:背包剩余容量
int memo[][] = new int[2][capcity + 1];
//base case
for(int i = 0; i <= capcity; i ++) {
//只考虑第一个,只要容量满足,就可以放入
if(weight[0] <= i)
memo[0][i] = value[0];
}
for(int i = 1; i < weight.length; i ++) {
for(int j = 0; j <= capcity; j ++) {
int tmp = i ;
//不考虑当前位置的石头
memo[i][j] = memo[i - 1][j];
//考虑且要放得下
if(j >= weight[i])
memo[i][j] = Math.max(memo[i][j], value[i] + memo[i - 1][j - weight[i]]);
}
}
return memo[weight.length - 1][capcity];
}
优化
可以发现i
行数据依赖于i - 1
行数据,所以我们只要重用空间就可以只使用2行记录状态。
//时间复杂度为O(C * n) 其中C为背包容量,n为石头个数
//空间复杂度为O(C * 2) 其中C为背包容量
private int findRes4(int[] weight, int[] value, int capcity) {
if(weight.length != value.length) return 0;
if(capcity <= 0)return 0;
if(weight.length == 0)return 0;
//行:是否考虑该位置的石头 0:只考虑第一个 1:考虑1 n:考虑n
//列:背包剩余容量
int memo[][] = new int[2][capcity + 1];
//base case
for(int i = 0; i <= capcity; i ++) {
//只考虑第一个,只要容量满足,就可以放入
if(weight[0] <= i)
memo[0][i] = value[0];
}
for(int i = 1; i < weight.length; i ++) {
for(int j = 0; j <= capcity; j ++) {
int tmp = i ;
//不考虑当前位置的石头 [0]的位置放偶数 [1]的位置放奇数
memo[i & 1][j] = memo[(i - 1) & 1][j];
//考虑且要放得下
if(j >= weight[i])
memo[i & 1][j] = Math.max(memo[i & 1][j], value[i] + memo[(i - 1) & 1][j - weight[i]]);
}
}
return memo[(weight.length - 1) & 1][capcity];
}
最佳
在此深入可以发现,每一行数据只依赖上一行数据的左边数据,所以我们可以将数据存储实现在一行上。
//时间复杂度为O(C * n) 其中C为背包容量,n为石头个数
//空间复杂度为O(C) 其中C为背包容量
private int findRes5(int[] weight, int[] value, int capcity) {
if(weight.length != value.length) return 0;
if(capcity <= 0)return 0;
if(weight.length == 0)return 0;
//行:是否考虑该位置的石头 0:只考虑第一个 1:考虑1 n:考虑n
//列:背包剩余容量
int memo[] = new int[capcity + 1];
//base case
for(int i = 0; i <= capcity; i ++) {
//只考虑第一个,只要容量满足,就可以放入
if(weight[0] <= i)
memo[i] = value[0];
}
//右边数据依赖于左边数据,所以只要开辟O(C)空间
for(int i = 1; i < weight.length; i ++) {
for(int j = capcity; j >= weight[i]; j --) {
memo[j] = Math.max(memo[j], value[i] + memo[j - weight[i]]);
}
}
return memo[capcity];
}
测试main函数
public static void main(String[] argc){
int[] weight = new int[]{1, 2, 3};
int[] value = new int[]{6, 10, 12};
int res = new Pakage01().findRes(weight, value, 5);
System.out.println(res);
}