多包放多个物品,判断是否放的下

前言

    最近做需求,碰到使用叠加券的情况。一笔订单,对于同一类型的优惠券可以叠加使用,但需要满足此订单匹配到的多个规则,规则有金额限制。
    转换问题描述:目前有多个物品,物品需要放在多个背包中。一个物品不可分割到多个包中,背包容量大小需要大于物品重量。有的背包只允许放一个物品,即使他的容量可以容纳下多个物品。有的背包允许放多个物品,只要他的容量能容纳下。现在需要判断多个背包能否放下多个物品,并返回如何放。

算法实现

package com.ykq;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.util.*;

/**
 * @author: ykq
 * @date: 2022/9/29 10:15
 * @Description:
 * 需求需要对于同一coupId的可叠加面额券,能够叠加使用
 * 同一coupId,对于不同的商品可能会过滤出不同的规则(多个背包),
 * 需要判断同一coupId的多张面额券(不可拆分),匹配到对应的多个规则(不可合并,如果合并,意味着一张券使用时匹配了两个规则)
 * 简单来说,多个背包(不可合并),放下多个物品(不可拆分)
 * 注意:不允许叠加使用的规则(背包),只允许匹配一张面额券(物品)
 */
@Slf4j
public class MultiPackUtil {

    /**
     * @Author ykq
     * @Date 2022/9/29 10:23
     * @Description 校验多个背包,能否放下多个物品
     */
    public static boolean checkPutDown(List<Package> parkList, List<Goods> goodsList) {
        // 将背包容量从大到小排序
        Collections.sort(parkList);
        // 将物品重量从大到小排序
        Collections.sort(goodsList);
        BigDecimal parkSum = parkList.stream().map(Package::getCapacity)
                .filter(Objects::nonNull).reduce(BigDecimal::add).get();
        BigDecimal goodsSum = goodsList.stream().map(Goods::getWeight)
                .filter(Objects::nonNull).reduce(BigDecimal::add).get();
        // 先校验包的总容量,物品总重量的大小
        if (parkSum.compareTo(goodsSum) < 0) {
            log.error("背包总容量限制:{} < 物品总重量:{}", parkSum, goodsSum);
            return false;
        }
        // 下面场景都是,包容量和 > 物品重量和
        // 如果只有一个包
        if (parkList.size() == 1) {
            Package park = parkList.get(0);
            // 不允许叠加使用,但有多个物品,则放不下
            if (!park.isOverLayUse() && goodsList.size() > 1) {
                log.error("只有一个背包:{}, 背包不允许放多个物品, 但物品有多个, 物品数量:{}", park.getParkId(), goodsList.size());
                return false;
            }
            // 其他场景,可以放下
            // 包括一个包,不允许叠加使用,有一个物品场景
            // 包括一个包,允许叠加使用,有多个物品场景
            // 将物品放入包中
            parkList.get(0).getGoodsList().addAll(goodsList);
            return true;
        }
        // 未使用物品栈
        LinkedList<Goods> goodsDeque = new LinkedList<>(goodsList);
        // 已使用物品栈
        LinkedList<Goods> removeGoodsDeque = new LinkedList<>();
        return checkPutDown2(parkList, goodsDeque, removeGoodsDeque);
    }

    /**
     * @Author ykq
     * @Date 2022/9/30 10:18
     * @Description 回溯法,递归判断是否能放下
     */
    private static boolean checkPutDown2(List<Package> parkList, LinkedList<Goods> goodsDeque,
                                  LinkedList<Goods> removeGoodsDeque) {
        // 如果有多个包,使用回溯法,一个个放
        for (int parkDos = 0; parkDos < parkList.size(); parkDos++) {
            Package park = parkList.get(parkDos);
            while(!goodsDeque.isEmpty()) {
                // 如果包总容量-已使用容量>物品重量
                // 获取栈顶元素
                Goods goods = goodsDeque.peekFirst();
                // 判断
                // 1.包不可放多个物品,没放物品时,包容量大于物品重量
                // 2.包可放物品,剩余包容量大于物品重量
                // 上述两种场景,可将物品放入包中
                if ((!park.isOverLayUse() && park.getGoodsList().isEmpty() && park.getCapacity().compareTo(goods.getWeight()) >= 0)
                    || (park.isOverLayUse() && park.getCapacity().subtract(park.getUsedWeight()).compareTo(goods.getWeight()) >= 0)){
                    // 将物品放入到包中
                    park.getGoodsList().add(goods);
                    // 已用容量增加
                    park.setUsedWeight(park.getUsedWeight().add(goods.getWeight()));
                    // 移除已使用的物品
                    goodsDeque.removeFirst();
                    // 将移除的物品放入被使用栈
                    removeGoodsDeque.addFirst(goods);
                    // 将后面的物品从头开始放
                    if (checkPutDown2(parkList, goodsDeque, removeGoodsDeque)) {
                        return true;
                    } else {
                        // 将被移除的元素,回滚到物品栈中
                        Goods removeGoods = removeGoodsDeque.removeFirst();
                        park.getGoodsList().remove(removeGoods);
                        goodsDeque.addFirst(removeGoods);
                        break;
                    }
                } else {
                    // 放不下就去下一个包
                    break;
                }
            }
            // 物品放完了,返回true
            if (goodsDeque.isEmpty()) {
                return true;
            }
        }
        return false;
    }

    /**
     * @Author ykq
     * @Date 2022/10/14 9:50
     * @Description 背包类
     */
    @Data
    public class Package implements Comparable<Package> {
        // 背包id
        private Long parkId;

        // 背包容量
        private BigDecimal capacity;

        // 是否允许放多个物品
        private boolean overLayUse;

        // 已放物品重量
        private BigDecimal usedWeight = BigDecimal.ZERO;

        // 放了哪些物品
        private List<Goods> goodsList = new ArrayList<>();

        /**
         * @Author ykq
         * @Date 2022/10/14 9:59
         * @Description 按背包不允许->允许排序,再按背包容量从大到小排序
         */
        @Override
        public int compareTo(Package other) {
            int thisOverLayUseInt = this.overLayUse ? 1 : 0;
            int otherOverLayUseInt = other.overLayUse ? 1 : 0;
            int order = thisOverLayUseInt - otherOverLayUseInt;
            if (order != 0) {
                return order;
            }
            return other.capacity.compareTo(this.capacity);
        }
    }

    /**
     * @Author ykq
     * @Date 2022/10/14 9:51
     * @Description 物品类
     */
    @Data
    public class Goods implements Comparable<Goods> {
        // 物品id
        private Long goodsId;

        // 物品重量
        private BigDecimal weight;

        /**
         * @Author ykq
         * @Date 2022/10/14 9:58
         * @Description 按物品重量从大到小排序
         */
        @Override
        public int compareTo(Goods other) {
            return other.weight.compareTo(this.weight);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值