【数组】-红包分配经典算法(二倍均值法 + 线段切割法)

写在前面

  最近想复习一下数据结构与算法相关的内容,找一些题来做一做。如有更好思路,欢迎指正。



一、场景描述

  红包分配的生成有两种经典方式:一种是预算(占用存储,增加IO),一种是实时计算。本文主要写一下实时计算的方式。


二、具体步骤

1.环境说明

名称说明
IntelliJ IDEA2023.3

2.概念

2.1 如何生成一个指定起止的随机数

public static void main(String[] args) {
    Random random = new Random();
    // 生成一个 [min, max) 之间的数
    int min = 1, max = 10;
    for (int i = 0; i < 10; i++) {
        int randomInt = min + random.nextInt(max - min);
        System.out.println(randomInt);
    }
}

3.代码

以下为Java版本实现:

package cn.thinkinjava.dsalg.array;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

/**
 * 经典红包分配算法
 * 红包的生成有2种方式:一种是预算(占用存储,增加IO),一种是实时计算
 *
 * 要求:
 * 1、所有人抢到的红包之和等于总金额
 * 2、参与抢红包的人,抢到时最少是0.01元
 *
 * @author qiuxianbao
 * @date 2025/02/17
 */
public class RedPacketsDistribute {

    public static void main(String[] args) {
        int totalAmount = 100 * 100;
        int totalNum = 10;

        List<Integer> redPackets0 = distributeRedPackets0(totalAmount, totalNum);
        System.out.println(redPackets0);
        System.out.println(redPackets0.stream().mapToInt(Integer::intValue).sum() == totalAmount);

        List<Integer> redPackets = distributeRedPackets(totalAmount, totalNum);
        System.out.println(redPackets);
        System.out.println(redPackets.stream().mapToInt(Integer::intValue).sum() == totalAmount);
    }

    /**
     * 线段切割法
     *
     * 思路:
     * 将总金额看作一条线段,随机在该线段上切割,
     * 切割点的数量等于红包数量减1,每个切割点之间的线段长度即为一个红包的金额
     *
     * @param totalAmount 总金额,单位分
     * @param totalNum  总人数
     * @return
     */
    private static List<Integer> distributeRedPackets(int totalAmount, int totalNum) {
        List<Integer> redPackets = new ArrayList<>();
        Random random = new Random();
        // 生成切割点
        List<Integer> cutPoints = new ArrayList<>();
        for (int i = 0; i < totalNum - 1; i++) {
            // 生成 [1, totalAmount-1]
            int cutPoint = 1 + random.nextInt(totalAmount - 1);
            cutPoints.add(cutPoint);
        }

        Collections.sort(cutPoints);

        int lastCutPoint = 0;
        for (int cutPoint : cutPoints) {
            redPackets.add(cutPoint - lastCutPoint);
            lastCutPoint = cutPoint;
        }

        // 最后一个红包的金额为最后一个切割位置与总金额之间的差值
        redPackets.add(totalAmount - lastCutPoint);
        return redPackets;
    }


    /**
     * 雨露均沾,二倍均值法
     *
     * 除了最后一个人,其他人最多允许抽到剩余平均值的2倍,目的是为了把每个人可能抽到的最高金额强行降低
     * 缺点:最后一个人分到的钱可能很大
     *
     * 思路:
     * 每次随机分配时,从剩余金额中随机取一个值,这个值是剩余金额的2倍除以剩余红包数再减去一个很小的值,
     * 以确保每次分配的金额不会过大或过小
     *
     * @param totalAmount 总金额,单位分
     * @param totalNum  总人数
     * @return
     */
    private static List<Integer> distributeRedPackets0(int totalAmount, int totalNum) {
        List<Integer> redPackets = new ArrayList<>();
        Random random = new Random();
        // 剩余金额, 剩余人数
        int restAmount = totalAmount;
        int restNum = totalNum;

        for (int i = 0; i < totalNum - 1; i++) {
            // 单个红包不低于1分
            int min = 1;
            int max = restAmount / restNum * 2 - 1;
            if (max < min) {
                max = min;
            }

            // 生成一个 [min, max] 之间的随机数
            int randomAmount = min + random.nextInt(max - min + 1);
            redPackets.add(randomAmount);
            restAmount -= randomAmount;
            restNum--;
        }

        // 最后一个红包直接分配剩余的金额
        redPackets.add(restAmount);
        return redPackets;
    }

}


三、参考资料

破解红包算法!为啥“手气最佳”总是别人?
微信红包的架构设计简介
二倍均值随机算法之抢拼手气红包场景应用


写在后面

  如果本文内容对您有价值或者有启发的话,欢迎点赞、关注、评论和转发。您的反馈和陪伴将促进我们共同进步和成长。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值