背包问题
背包问题的各种衍生问题多了去了,本次针对的是最简单的0-1背包,顺便说说几个困扰了我好多天的疑惑。
示例1
输入:N = [8, 1, 12, 7, 9, 7], V = 11
输出:1
解释:快递箱容量V为11,物品体积数组N为[8, 1, 12, 7, 9, 7],最优解为取体积为
1的快递和体积为9的快递,即快递箱剩余最小空间为 11-(1+9)=1
示例2
输入:N = [8, 2, 12, 7, 9, 7], V = 11
输出:0
解释:11-(2+9) = 0
示例3
输入:N = [8, 2, 12, 7, 9, 7], V = 100
输出:55
解释:100-(8+2+12+7+9+7) = 55
本题可以抽象为在N.length个物品中取物品并得到最大价值,容器的容量是V,价值和体积为1:1
(当然了实在不喜欢用动态规划可以整个bit数试试)
二维背包
首先明确一下定义的下标和值。
f[i][j]
i的话是指代的前i个物品,j是容量。值的话就是价值了。
解法的话就是先赋初值,之后后一个状态可以根据前一个状态得到。
public int minRemainingSpace(int[] N, int V) {
int[][] f = new int[N.length][V+1];
// 注意初始化
for(int i=N[0];i<=V;i++){
f[0][i]=N[0];
}
for (int i = 1; i < N.length; i++)
for(int j=0;j<=V;j++){
f[i][j]=f[i-1][j];
if(j>=N[i]){
f[i][j]=Math.max(f[i][j],f[i-1][j-N[i]]+N[i]);
}
}
return V-f[N.length-1][V];
}
为什么j的遍历方式,正向,反向都可以呢?
这个问题主要在于f[i][j]
的状态,主要是和f[i-1][~]
状态有关,跟f[i][~]
没啥关系,而i-1的状态前面已经算过了,一直向前追溯的话会追溯到初始化的时候。
为什么j遍历的左右边界不能是N[i]呢?
可以考虑这样一种情况,[2,4,3]背包容量是5,遍历的时候j从N[1]开始,f[1][2]
,f[1][3]
这些赋值都会被跳过只能使用默认的0值。
一维背包
个人感觉一维背包其实更好理解。
f[i]
下标就是容量,值的话就价值。
class Solution {
public int minRemainingSpace(int[] N, int V) {
int[] f = new int[V+1];
for (int i = 0; i < N.length; i++)
for (int j = V; j >= N[i]; j--)
if (f[j] < f[j - N[i]] + N[i])
f[j] = f[j - N[i]] + N[i];
return V - f[V];
}
}
外层循环主要是判断第i个物品能不能放进背包,如果能放进去就比较一下,看看价值多不多。
此处其实隐含了一些条件(同时也是为什么j不能比N[i]小),例如:如果放不下的话,这个值就不会发生变化。如果能放下的话,值就可能发生变化。
为什么不能正向放入呢?
还是举上面的例子[2,4,3]背包容量是5。
f[i]
和前面的f[~]
相关联,因为每个物品只能放入一次。要确保求f[5]
的时候只放入了一次物品,那么只能先给f[5]
赋值了。
如果正向放入的话,f[3]
赋值为2,当轮到f[5]
的时候,看到还有地方能放东西,f[5]
会再放一次2。