简单理解背包问题

背包问题

背包问题的各种衍生问题多了去了,本次针对的是最简单的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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值