【Java校招面试】实战算法(一)——背包问题总结


前言

“实战算法”是本专栏的第三个部分,本篇博文是第一篇博文,主要收集了常见的背包问题及其变种,如有需要,可:

  1. 点击这里,返回本专栏的索引文章
  2. 点击这里,前往下一篇《【Java校招面试】实战算法(二)——2的N次方(迅雷)》

一、01背包

  1. 说明: 所有东西都只有一个,可以选择装或者不装

  2. 公式:

    public static int solve(int[] costs, int[] values, int budget, int n){
        int[] pkg = new int[budget + 1];
        for (int i = 0; i < n; i++){
            for (int j = budget; j >= costs[i]; j--)
                pkg[j] = Math.max(pkg[j], pkg[j - costs[i]] + values[i]);
        }
        return pkg[budget];
	}

二、变种01背包1——多重背包

  1. 说明: 每个东西的数量不一样,是一个有限的数

  2. 公式:

    public static int solve(int[] costs, int[] values, int[] counts, int budget, int n) {
        int[] pkg = new int[budget + 1];
        for (int i = 0; i < n; i++) {
            while (counts[i]-- > 0) {
                for (int j = budget; j >= costs[i]; j--) {
                    pkg[j] = Math.max(pkg[j], pkg[j - costs[i]] + values[i]);
                }
            }
        }
        return pkg[budget];
	}

注: 有一种特殊情况,即cost就是value(如一定容积的牛车拉草,拉的体积越大越好),此时调用solve的时候传入前两个参数都是cost或者value即可;


三、变种01背包2

  1. 说明: 多一个交易限制tradeFloor, 即budget必须大于tradeFloor[i],才可以用cost[i]买物品i,详见Proud Merchants。

  2. 公式:

    private static class Merchandise{
        int cost;
        int tradeFloor;
        int value; 
        public Merchandise(int cost, int tradeFloor, int value){
            this.cost = cost;
            this.tradeFloor = tradeFloor;
            this.value = value;
        }
	}

    public static int solve(List<Merchandise> list, int budget, int n){
        Collections.sort(list, (a, b) -> (a.tradeFloor - a.cost) - (b.tradeFloor - b.cost));
        int[] pkg = new int[budget + 1];
        for (int i = 0; i < n; i++){
            Merchandise m = list.get(i);
            for (int j = budget; j >= m.tradeFloor; j--){
                pkg[j] = Math.max(pkg[j], pkg[j - m.cost] + m.value);
            }
        }
        return pkg[budget];
    }

注: 先要按照tradeFloor[i] – cost[i]来排序,然后和正常01基本一样,就是第二层循环里的循环条件要把cost[i]换成tradeFloor[i],即达到交易条件才能交易。


四、完全背包

  1. 说明: 所有东西的数量都是无限的

  2. 公式:

    public static int solve(int[] costs, int[] values, int budget, int n){
        int[] pkg = new int[budget + 1];
        for (int i = 0; i < n; i++){
            for (int j = costs[i]; j <= budget; j++)
                pkg[j] = Math.max(pkg[j], pkg[j - costs[i]] + values[i]);
        }
        return pkg[budget];
	}

注: 和01背包的区别是,01背包是逆序递减,完全背包是顺序递增


五、变种完全背包

  1. 说明: 求最少能装多少价值,把性价比最低的装进去

  2. 公式:

    public static int solve(int[] costs, int[] values, int budget, int n, int maxCost){
        int[] pkg = new int[budget + 1];
        Arrays.fill(pkg, 1, budget + 1, maxCost + 1);
        for (int i = 0; i < n; i++){
            for (int j = costs[i]; j <= budget; j++){
                pkg[j] = Math.min(pkg[j], pkg[j - costs[i]] + values[i]);
            }
        }
        return pkg[budget];
	}

注:
   因为求最大时,Integer.minValue + 多少都不会溢出,但是 Integer.maxValue + 任何数都溢出,所以这里多了一个参数maxCost,即最大的价格,填充初始数组时需要使用maxCost + 1,判断是否有可行的结果就判断返回值是否等于maxCost + 1,等于就不存在,否则返回值即是所求最小值。
   和最大值不同之处在于,不用Integer.MinValue填充数组了,换用上述正值maxCost + 1,比较也不用max了,换用min。
   Arrays.fill有坑,填充的时候不包含toIndex。


六、二维背包

  1. 说明: 就是花费和预算有两个维度,比如质量和体积

  2. 公式: (展示的是二维完全背包)

    public static int solve(int[] costs1, int[] costs2, int[] values, int budget1, int budget2, int n) {
        int[][] pkg = new int[budget1 + 1][budget2 + 1];
        for (int i = 0; i < n; i++) {
            for (int j = costs1[i]; j <= budget1; j++) {
                for (int k = costs2[i]; k <= budget2; k++) {
                    pkg[j][k] = Math.max(pkg[j][k], pkg[j - costs1[i]][k - costs2[i]] + values[i]);
                }
            }
        }
        return pkg[budget1][budget2];
	}

注: 多一维,解向量就多一维,循环就多一层。


七、分组背包

  1. 说明: 有多组物品,每组物品最多只能选一个,也可以一个都不选

  2. 公式:

    private static class CostAndValue{
        int cost;
        int value;
        public CostAndValue(int cost, int value){
            this.cost = cost;
            this.value = value;
        }
    }

    public static int solve(List<List<CostAndValue>> groups, int budget) {
        int[] pkg = new int[budget + 1];
        groups.forEach((group) -> {
            for (int j = budget; j >= 0; j--) {
                for (CostAndValue cav : group) {
                    if (j >= cav.cost) {
                        pkg[j] = Math.max(pkg[j], pkg[j - cav.cost] + cav.value);
                    }
                }
            }
        });
        return pkg[budget];
	}

注: 注意三层循环加一个判断的结构

	for (所有的组k)
	    for (int j = V; j >= 0; j--)
	        for (所有属于组k的i)
	            if (j >= w[i])
	            	f[j] = max(f[j], f[j - w[i]] + v[i])

八、注意

初始化pkg时,如果要求结果必须把budget用完,需要pkg[0] = 0,其他位全是Integer.MIN_VALUE,如果不要求用完budget,全部是0即可。


后记

这些都是博主在实战过程中遇到的各种背包问题,及其解法,希望对大家有用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IMplementist

你的鼓励,是我继续写文章的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值