题目
给你⼀个可装载重量为 W 的背包和 N 个物品,每个物品有重量和价值两个属性。其中第 i 个物品的重量为 wt[i] ,价值为 val[i] ,现在让你⽤这个背包装物品,最多能装的价值是多少?
举个简单的例⼦,输⼊如下:
N = 4, W = 7
wt = [2 , 3 , 4 , 5]
val = [3 , 4 , 5 , 7]
算法返回:10 ,解释:选择重量为2和5的收益最大,为10.
分析
背包问题难点在于构建dp数组,
int[][] dp = new int[N + 1][W + 1];
了解动态规划的,大多数人都可以想到,这样定位dp数组,但是总是无法理解数组中N的含义。至少这个问题我困惑了很久。
N表示前n个元素,
W表示背包的总重量,
dp表示前N个元素在背包可容量总重量为W时的最大收益
为什么这里N要表示前N个元素
翻看了很多文章,大多数文章没有讲清楚这个点。为什么这样构建dp。
1.假设现在N表示选择物品的数量
那么dp[1][4]则表示选择一个物品,总重量不超过4的最大收益,即为dp[1][4]=5;
这样选择也可以实现,只是复杂度更高
每次在选择的时候我们需要比较选择哪个物品更优,且记录下哪些物品已经被选择过。过程较为繁琐。
繁琐就意味着状态转移方程难构建
N表示前n个元素
如果表示前n个元素,我们可以看下,又怎么方便了。这种表示让计算更方便了,但是确实增大了理解难度
现在,dp[1][4]则表示选择前1个物品,总重量不超过4的最大收益,即为dp[1][4]=3(只能选择第一个);
此时,dp状态转移方程也就只是放和不放的问题
当W<w(i)时,该物品已经放不下,所以此时
dp[i][w] = dp[i-1][w](不装入第i个元素的最大收益);
当W>=w(i)时,该物品可以放不下,
所以此时
dp[i - 1][w - wt[i-1]] + val[i-1]为装入第i个元素的最大收益;
dp[i - 1][w]为不装入第i个元素的最大收益;
// 装⼊或者不装⼊背包,择优
dp[i][w] = Math.max(dp[i - 1][w - wt[i-1]] + val[i-1],
dp[i - 1][w]);
dp[i - 1][w]);
注意,这种表示方法,就需要我们的状态转移,是由前往后推的,因为后面会依赖于前面的结果
1.所以有了第一层for循环,表示考虑第一个元素要不要放,第二个元素要不要放,第N个元素要不要放
//第一层遍历,第几个元素
for (int i = 1; i <=N; i++) {
......
}
2. 还有一个变量为重量,所以需要还有一个w的遍历
for (int i = 1; i <=N; i++) {
//第二层遍历,重量
for (int w= 1; w <= W; w++) {
.......
}
}
3. 结合上面的比较分析,有了以下的代码
//第一层遍历,第几个元素
for (int i = 1; i <=N; i++) {
//第二层遍历,重量
for (int w= 1; w <= W; w++) {
//如果当前背包可放入重量小于wt元素的重量,则无法放入背包
//此时dp[i][j]=dp[i-1][j];
if (w < wt[i - 1]) {
dp[i][w] = dp[i-1][w];
}else{
// 装⼊或者不装⼊背包,择优
dp[i][w] = Math.max(dp[i - 1][w - wt[i-1]] + val[i-1],
dp[i - 1][w]);
}
}
}
代码
public class PackDemo {
public static void main(String[] args) {
int N = 4;
int W = 7;
int[] wt = { 2 , 3 , 4 , 5};
int[] val = { 3 , 4 , 5 , 7};
//
// int[] wt = {5, 2 , 3 , 4};
// int[] val = { 2,3 , 4 , 5 };
getMaxVal(N, W, wt, val);
System.out.println("-----------------------");
}
/*
0-1背包问题,每个东西只能放一次
*/
private static int getMaxVal(int N, int W, int[] wt, int[] val) {
//构建dp数组,变化的维度为n和w,注意此处的N为前N个元素
int[][] dp = new int[N + 1][W + 1];
//第一层遍历,数量
for (int i = 1; i <=N; i++) {
//第二层遍历,重量
for (int w= 1; w <= W; w++) {
//如果当前背包可放入重量小于wt元素的重量,则无法放入背包
//此时dp[i][j]=dp[i-1][j];
if (w < wt[i - 1]) {
dp[i][w] = dp[i-1][w];
}else{
// 装⼊或者不装⼊背包,择优
dp[i][w] = Math.max(dp[i - 1][w - wt[i-1]] + val[i-1],
dp[i - 1][w]);
}
}
}
//打印数组结果查看下
for (int[] ints : dp) {
for (int anInt : ints) {
System.out.print(anInt + " ");
}
System.out.println();
}
return dp[N][W];
}
}
疑问
同样的参数,如果输入N=2,W=7,得到的结果是7,为什么不是重量2加上重量5的收益10?
请注意,这是一个思维误区,至少我陷入过这个误区。仔细观察我们的dp是定义的什么内容,n表示的是前N个元素,而不是所有元素。所于得到的结果表示的是前2个元素在重量为7的背包下可以得到的最大收益。
在使用时,也请注意,N的大小要等于数组的长度,才可以这样使用