红包算法

规则:

  1. 所有人抢到金额之和等于红包金额,不能超过,也不能少于
  2. 每个人至少抢到一分钱
  3. 要保证所有人抢到金额的几率相等

一、二倍均值法

剩余红包金额M,剩余人数N,那么:每次抢到金额=随机(0,M/N2)
保证了每次随机金额的平均值是公平的
假设10人,红包金额100元
第一人:100/10
2=20,随机范围(0,20),平均可以抢到10元
第二人:90/92=20,随机范围(0,20),平均可以抢到10元
第三人:80/8
2=20,随机范围(0,20),平均可以抢到10元
以此类推,每次随机范围的均值是相等的
缺点:除了最后一次,任何一次抢到的金额都不会超过人均金额的两倍,并不是任意的随机

    /**
     * 拆分红包  -- 二倍均值法
     * @param totalAmount 总金额(以分为单位)
     * @param totalPeopleNum 总人数
     * @return
     */
     public static List<Integer> divideRedPackage(Integer totalAmount,Integer totalPeopleNum){
         List<Integer> amountList = new ArrayList<>();
         Integer restAmount =  totalAmount;
         Integer restPeopleAmount = totalPeopleNum;
         Random random = new Random();
         for(int i = 0;i<totalPeopleNum-1;i++){
             //随机范围:【1,剩余人均金额的2倍-1】分
             int amount = random.nextInt(restAmount / restPeopleAmount * 2 -1)+1;
             restAmount -= amount;
             restPeopleAmount--;
             amountList.add(amount);
         }
         amountList.add(restAmount);
         return amountList;
     }

二、线段分割法

把红包总金额想象成一条很长的线段,而每个人抢到的金额,则是这条主线段所拆分出的若干子线段。

当N个人一起抢红包的时候,就需要确定N-1个切割点。
因此,当N个人一起抢总金额为M的红包时,我们需要做N-1次随机运算,以此确定N-1个切割点。
随机的范围区间是(1, M)。当所有切割点确定以后,子线段的长度也随之确定。这样每个人来抢红包的时候,只需要顺次领取与子线段长度等价的红包金额即可。

这就是线段切割法的思路。在这里需要注意以下两点:
(1)当随机切割点出现重复,如何处理 — 重复了就重新切呗
(2)如何尽可能降低时间复杂度和空间复杂度 — 这里我用链表,牺牲时间换取空间(排了个序),也可以牺牲空间节省时间(大数组)

    public static List<Integer> divideRedPackage2(Integer totalAmount,Integer totalPeopleNum) {
        List<Integer> list=new ArrayList<>();
        List<Integer> result=new ArrayList<>();
        Random random = new Random();

        while (list.size()<totalPeopleNum) {
            int i=random.nextInt(totalAmount-1)+1;//最低1分钱
            if(list.indexOf(i)<0){//非重复切割添加到集合
                list.add(i);
            }
        }
        Collections.sort(list);
        int  flag=0;
        int fl=0;
        for (int i=0;i<list.size();i++) {
            int temp=list.get(i)-flag;
            flag=list.get(i);
            fl+=temp;
            result.add(temp);
        }
        //最后一个红包
        result.add(totalAmount-fl);
        System.out.println(result.stream().mapToInt(p ->p).sum());
        return result;
    }
    

三、红包面试题

    //最少分得红包数
    private static final double min = 1;
    //最多分得红包数占比
    private static final double percentMax = 0.3;

    /**
     *
     * 红包算法,给定一个红包总金额和分红包的人数,输出每个人随机抢到的红包数量。
     * 要求:
     * 1. 每个人都要抢到红包,并且金额随机
     * 2. 每个人抢到的金额数不小于1
     * 3. 每个人抢到的金额数不超过总金额的30%
     * 例如总金额100元,人数10,输出【19 20 15 1 25 14 2 2 1 1】
     * @param money 总金额
     * @param peopleNum 总人数
     * @return
     */
    public static List<Integer> allocateMoney(double money, int peopleNum) {
        List<Integer> list = new ArrayList<>();
        double minMoney = min;
        double maxMoney = percentMax * money;
        int shareMoney = 0;
        double sum = 0;
        for (int i = 0; i < peopleNum; i++) {
            double max =  money - maxMoney * (peopleNum - i - 1);
            double min =  money - minMoney * (peopleNum - i - 1);
            minMoney = minMoney >max? minMoney :max;
            maxMoney = maxMoney < min ? maxMoney : min;
            shareMoney = (int) Math.floor((maxMoney - minMoney) * Math.random() + minMoney);
            money = money - shareMoney;
            sum += shareMoney;
            list.add(shareMoney);
        }
        System.out.println("要分配的红包总额为:" + sum + "元");
        return list;
    }

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值