利用动态规划解决购物中的最佳搭配问题

问题

商品总价 4800,共有以下几种充值赠送活动,问怎么充值购买最划算且最方便?

  • 充值 200 赠送 10
  • 充值 300 赠送 15
  • 充值 500 赠送 25
  • 充值 1000 赠送 50
  • 充值 2000 赠送 100
  • 充值 5000 赠送 250

实现


import java.util.*;

/**
 * 商品总价 4800,问怎么充值购买最划算且最方便?
 * 有以下优惠方案:
 * 充值 200 赠送 10
 * 充值 300 赠送 15
 * 充值 500 赠送 25
 * 充值 1000 赠送 50
 * 充值 2000 赠送 100
 * 充值 5000 赠送 250
 *
 * @author 杨贤达
 * @date 2019-11-13
 */
public class Solution {

    private static final Map<Integer, Integer> SOURCE = new LinkedHashMap<>();

    private static final int TARGET = 4800;

    private static Integer minNum = TARGET;

    // 只是为了简单去重用,未实现排序逻辑
    private static Set<List<Integer>> resultSet = new TreeSet<>((l1, l2) -> l1.containsAll(l2) && l2.containsAll(l1) ? 0 : 1);

    static {
        SOURCE.put(200, 10);
        SOURCE.put(300, 15);
        SOURCE.put(500, 25);
        SOURCE.put(1000, 50);
        SOURCE.put(2000, 100);
        SOURCE.put(5000, 250);
    }

    public static void main(String[] args) {
        fun(new ArrayList<>(), 0);
        printResult(resultSet);
    }

    private static void printResult(Set<List<Integer>> resultSet) {
        for (List<Integer> list : resultSet) {
            Map<Integer, Integer> map = new HashMap<>();
            for (Integer integer : list) {
                if (map.containsKey(integer)) {
                    map.put(integer, map.get(integer) + 1);
                } else {
                    map.put(integer, 1);
                }
            }
            Integer actualPayment = list.stream().reduce(0, Integer::sum);
            Integer totalAmount = list.stream().reduce(0, (a, b) -> a + b + SOURCE.get(b));
            System.out.println("==================================================");
            System.out.println("搭配集合 = " + map);
            System.out.println("实际支付 = " + actualPayment);
            System.out.println("实际支付 + 赠送金额 = " + totalAmount);
            System.out.println("浪费金额 = " + (totalAmount - TARGET));
            System.out.println("操作次数 = " + list.size());
        }
    }

    private static void fun(List<Integer> list, int total) {
        int waste = total - TARGET;
        // 浪费超过 150 元,直接不予考虑
        if (waste > 150) {
            return;
        }
        // 必须大于指定金额,不能小于,即浪费的金额必须为正数
        if (waste > 0 && waste < minNum) {
            minNum = waste;
        }
        // 收集筛选后的结果
        if (total - TARGET > 0 && total - TARGET < 150 && list.size() < 6) {
            resultSet.add(list);
        }
        for (Map.Entry<Integer, Integer> entry : SOURCE.entrySet()) {
            Integer key = entry.getKey();
            Integer value = entry.getValue();

            list.add(key);

            List<Integer> copy = (List<Integer>) ((ArrayList<Integer>) list).clone();
            total = total + key + value;
            fun(copy, total);

            list.remove(key);
            total = total - key - value;
        }
    }
}

输出

==================================================
搭配集合 = {2000=2, 200=3}
实际支付 = 4600
实际支付 + 赠送金额 = 4830
浪费金额 = 30
操作次数 = 5
==================================================
搭配集合 = {2000=2, 200=2, 300=1}
实际支付 = 4700
实际支付 + 赠送金额 = 4935
浪费金额 = 135
操作次数 = 5
==================================================
搭配集合 = {2000=1, 500=1, 1000=2, 200=1}
实际支付 = 4700
实际支付 + 赠送金额 = 4935
浪费金额 = 135
操作次数 = 5
==================================================
搭配集合 = {2000=2, 500=1, 200=1}
实际支付 = 4700
实际支付 + 赠送金额 = 4935
浪费金额 = 135
操作次数 = 4
==================================================
搭配集合 = {2000=2, 200=3}
实际支付 = 4600
实际支付 + 赠送金额 = 4830
浪费金额 = 30
操作次数 = 5
==================================================
搭配集合 = {2000=1, 500=1, 1000=2, 200=1}
实际支付 = 4700
实际支付 + 赠送金额 = 4935
浪费金额 = 135
操作次数 = 5
==================================================
搭配集合 = {2000=1, 1000=2, 300=2}
实际支付 = 4600
实际支付 + 赠送金额 = 4830
浪费金额 = 30
操作次数 = 5
==================================================
搭配集合 = {2000=2, 300=2}
实际支付 = 4600
实际支付 + 赠送金额 = 4830
浪费金额 = 30
操作次数 = 4
==================================================
搭配集合 = {2000=2, 200=2, 300=1}
实际支付 = 4700
实际支付 + 赠送金额 = 4935
浪费金额 = 135
操作次数 = 5
==================================================
搭配集合 = {2000=1, 1000=2, 300=2}
实际支付 = 4600
实际支付 + 赠送金额 = 4830
浪费金额 = 30
操作次数 = 5
==================================================
搭配集合 = {2000=2, 200=3}
实际支付 = 4600
实际支付 + 赠送金额 = 4830
浪费金额 = 30
操作次数 = 5
==================================================
搭配集合 = {2000=2, 200=2, 300=1}
实际支付 = 4700
实际支付 + 赠送金额 = 4935
浪费金额 = 135
操作次数 = 5
==================================================
搭配集合 = {2000=1, 500=1, 1000=2, 200=1}
实际支付 = 4700
实际支付 + 赠送金额 = 4935
浪费金额 = 135
操作次数 = 5
==================================================
搭配集合 = {2000=2, 200=3}
实际支付 = 4600
实际支付 + 赠送金额 = 4830
浪费金额 = 30
操作次数 = 5
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
买书问题 dp实现 题目:买书 有一书店引进了一套书,共有3卷,每卷书定价是60元,书店为了搞促销,推出一个活动,活动如下: 如果单独购买其一卷,那么可以打9.5折。 如果同时购买两卷不同的,那么可以打9折。 如果同时购买三卷不同的,那么可以打8.5折。 如果小明希望购买第1卷x本,第2卷y本,第3卷z本,那么至少需要多少钱呢?(x、y、z为三个已知整数)。 1、过程为一次一次的购买,每一次购买也许只买一本(这有三种方案),或者买两本(这也有三种方案), 或者三本一起买(这有一种方案),最后直到买完所有需要的书。 2、最后一步我必然会在7种购买方案选择一种,因此我要在7种购买方案选择一个最佳情况。 3、子问题是,我选择了某个方案后,如何使得购买剩余的书能用最少的钱?并且这个选择不会使得剩余的书为负数 。母问题和子问题都是给定三卷书的购买量,求最少需要用的钱,所以有"子问题重叠",问题三个购买量设置为参数, 分别为i、j、k。 4、的确符合。 5、边界是一次购买就可以买完所有的书,处理方式请读者自己考虑。 6、每次选择最多有7种方案,并且不会同时实施其多种,因此方案的选择互不影响,所以有"子问题独立"。 7、我可以用minMoney[i][j][k]来保存购买第1卷i本,第2卷j本,第3卷k本时所需的最少金钱。 8、共有x * y * z个问题,每个问题面对7种选择,时间为:O( x * y * z * 7) = O( x * y* z )。 9、用函数MinMoney(i,j,k)来表示购买第1卷i本,第2卷j本,第3卷k本时所需的最少金钱,那么有: MinMoney(i,j,k)=min(s1,s2,s3,s4,s5,s6,s7),其s1,s2,s3,s4,s5,s6,s7分别为对应的7种方案使用的最少金钱: s1 = 60 * 0.95 + MinMoney(i-1,j,k) s2 = 60 * 0.95 + MinMoney(i,j-1,k) s3 = 60 * 0.95 + MinMoney(i,j,k-1) s4 = (60 + 60) * 0.9 + MinMoney(i-1,j-1,k) s5 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1) s6 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1) s7 = (60 + 60 + 60) * 0.85 + MinMoney(i-1,j-1,k-1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值