VIVO笔试题——背包问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012525096/article/details/91345223

背景:VIVO提前批笔试题遇到了01背包,就记得动态规划动态规划,记得表格法,突然失忆怎么写。

来自背包九讲

01背包

有 N 件物品和一个容量为 V 的背包。放入第 i 件物品耗费的容量是 Ci,得到的
价值是 Wi。求解将哪些物品装入背包(每个物品只可放一件物品)可使价值总和最大(最优化问题)?

思路

自顶而下的方式去思考问题,设f(i,v)为选取前 i 件物品装入容量为 v 的背包中的物品最大值;那么f(i,v)等于不选 i 物品的 f 和选 i 物品(判断容量范围)的 f 的最大值。
即:f(i,v) = max{f(i-1,v),f(i-1,v-Ci)+Wi}
思考问题用自顶而下的方式,但是编码的话,若是自顶而下必然会出现“子问题的重叠”而导致的时间复杂度的提高。
那么就采用,自下而上的方式去编码,既然f(i,v) = max{f(i-1,v),f(i-1,v-Ci)+Wi},那么就从f(0,v)、f(1,v)开始循环去计算,避免了重复的子问题的计算。

编码

(1)初始化耗费容量和总容量(递增)的二维dp数组,第0行第0列初始化为0;
(2)编写状态转移方程,注意每一列是从 Ci 到 V或者从1到V + 容量判断

public static int dpCore1(int V, int[] cost, int[] value) {
        int N = cost.length;
        // 初始化数组表格
        int[][] dp = new int[N + 1][V + 1];
        // 前0个物品,全是0
        for (int i = 0; i < N + 1; i++) {
            dp[i][0] = 0;
        }
        // 没有容量,全是0
        for (int i = 0; i < V + 1; i++) {
            dp[0][i] = 0;
        }
        for (int i = 1; i < N + 1; i++) {
            for (int j = cost[i-1]; j < V + 1; j++) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - cost[i - 1]] + value[i - 1]);
            }
        }
        return dp[N][V];
    }

空间优化

思考,f(i,v) = max{f(i-1,v),f(i-1,v-Ci)+Wi} -> f(v) = max{f(v),f(v-Ci)+Wi}
用一个f[0…v]保存当前价格最大值,那么将第二个循环从V到Ci倒着来遍历,这样就保证了在推f[v]的时候用的是上一轮计算的结果,即f[v-ci]保存的状态是上一轮计算的f(i-1,v-ci)(因为倒着来,较小的值还没更新)

编码

    public static int dpCore2(int V, int[] cost, int[] value) {
        int N = cost.length;
        // 初始化dp一维数组
        int[] dp = new int[V + 1];
        for (int i = 1; i < N + 1; i++) {
            for (int j = V; j >= cost[i - 1]; j--) {
            	// 注意此处是倒着来的,j - cost[i - 1]此时是比j小的数字,由于是倒着来的,故还没更新,保存的内容是上一轮的
                dp[j] = Math.max(dp[j], dp[j - cost[i - 1]] + value[i - 1]);
            }
        }
        return dp[V];
    }

初始化问题

思想来自背包九讲:若要求背包必须装满,那么初始化dp数组时,除了dp[0]为0外其他的都为负无穷;若不要求装满,而是价值尽量大,那么dp全部初始化为0。含义就是:若果必须装满,那么只有在容量为0且什么都不装的情况下才能满足要求,容量>0没有合法解;若不要求装满,那在各个容量情况下都可以不放而为0。

完全背包问题

完全背包就是物品不止一个,可以拿多次。
题目:有 N 种物品和一个容量为 V 的背包,每种物品都有无限件可用。放入第 i 种物品的费用是 Ci,价值是 Wi。求解:将哪些物品装入背包,可使这些物品的耗费的费用总和不超过背包容量,且价值总和最大。

思路1

类似01背包的思想,只是每一步max中多个而不是2个的对比状态,其转移方程如下:
f(i,v) = max{f(i-1,v-k*Ci)+k*Wi | 0≤k*Ci≤v}
该方程的时间复杂度为O(NV*∑(v/ci)),其中∑(v/ci)是求解每一步f(i,v)所需要的时间复杂度。

优化

首先将大于 V 的物品去掉,在将费用相同价值最高的选出来,再将Ci≤Cj && Wi≥Wj的 j 物品直接去掉,在用上述方法进行计算。

思路2

转换成01背包来求解,在给定 V 的条件下,将每一个物品拆分成[V/Ci]个物品,且拆分出来的物品的价值W为k*Wi,求解这个新的01背包问题。
更高效的转换方法:把第 i 件物品拆分成费用为Ci2k,价值为Wi2k的若干物品,其中k取遍满足Ci2k的非负整数。思想在于,任何一个数字都可以用二进制数表示,即多个2k才表示。这样每件物品拆分成O(log[V/Ci])件物品。

扫描二维码关注公众号,回复: 6554063 查看本文章

正统思路3

将01背包优化后的流程中的倒序遍历改成正序遍历。
for(int j = Ci ; i < V+1 ; j++)
之前倒序是为了f[v]是上次i-1时保存的值,而正序是为了选过之后还可以选(小的已经选过,再次比较),选其中的最大值。

多重背包问题

不是无限可拿,而是最多Mi件可用,耗费Ci、价值Wi

思路1

还是用类似于01背包的思路,转移方程如下:
f(i,v) = max{f(i-1,v-k*Ci)+k*Wi | 0≤k*Ci≤v,0≤k≤Mi},时间复杂度O(V*∑Mi*∑(v/ci))

思路2

还是拆分的思想,每件物品的花费和价值分别乘系数就是拆分后物品的属性,对于给的Mi,可以拆分成1,2,22,23,Mi-2k+1的系数,且k是满足Mi-2k+1>0的最大整数。若Mi=13,则可以分系数为1,2,4,6的4件物品。

猜你喜欢

转载自blog.csdn.net/u012525096/article/details/91345223
今日推荐