如何实现抢红包算法,怎样才能做到随机公平

    科技2022-07-13  126

    抢红包算法

    🧧问题描述1. 基础实现-非公平2. 二倍均值法3. 线段切割法

    🧧问题描述

    例如一个人在群里发了10块钱的红包,群里有5个人一起来抢红包,每人抢到的金额随机分配。

    红包功能需要满足哪些具体规则呢?

    所有人抢到的金额之和要等于红包金额,不能多也不能少每个人至少抢到1分钱要保证红包拆分的金额尽可能分布均衡,不要出现两极分化太严重的情况

    【金额处理】

    先将输入的totalMoney(元)扩大100倍,换做分在输出时用BigDecimal做转换 // 换做分输出 for (int i = 0; i < amount; i++) { System.out.println("抢到金额" + new BigDecimal(redPackage[i]).divide(new BigDecimal(100))); }

    1. 基础实现-非公平

    【思路】

    每次抢红包的时候直接随机就好啦,随机的上限是剩余的红包金额每次抢到的金额 = 随机区间 ( 0, 剩余金额 ) public class _01_RedPackage { public static void main(String[] args) { Scanner in = new Scanner(System.in); int total = in.nextInt() * 100; in.nextLine(); int amount = in.nextInt(); int[] redPackage = new int[amount]; getRandomMoney(total, amount, redPackage); // 换做分输出 for (int i = 0; i < amount; i++) { System.out.println("抢到金额" + new BigDecimal(redPackage[i]).divide(new BigDecimal(100))); } } public static void getRandomMoney(int total, int amount, int[] redPackage) { final int MIN_RED = 1; Arrays.fill(redPackage, MIN_RED); int last = total - amount; Random random = new Random(); for (int i = 0; i < amount - 1; i++) { //rand.nextInt(MAX - MIN + 1) + MIN int randRedValue = random.nextInt(last - 1); redPackage[i] += randRedValue; last -= randRedValue; } redPackage[amount - 1] += last; } }

    【存在的问题】

    如果以这种方式随机,先抢的人会有很大优势,越往后的人随机到的平均金额越小。

    假设有10个人,红包总额100元。

    第一个人的随机范围是(0,100元),平均可以抢到50元。假设第一个人随机到50元,那么剩余金额是100-50 = 50 元。第二个人的随机范围是 (0, 50元),平均可以抢到25元。假设第二个人随机到25元,那么剩余金额是50-25 = 25 元。第三个人的随机范围是 (0, 25元),平均可以抢到12.5元。

    以此类推,每一次随机范围越来越小。

    2. 二倍均值法

    剩余红包金额M,剩余人数N,那么:

    每次抢到金额 = 随机区间(0, M/N*2)

    保证了每次随机金额的平均值是公平的

    假设10人,红包金额100元

    第一人:100/10*2=20,随机范围(0,20),平均可以抢到10元第二人:90/9*2=20,随机范围(0,20),平均可以抢到10元第三人:80/8*2=20,随机范围(0,20),平均可以抢到10元

    以此类推,每次随机范围的均值是相等的

    缺点:除了最后一次,任何一次抢到的金额都不会超过人均金额的两倍,并不是任意的随机

    public class _02_RedPackage { public static void main(String[] args) { Scanner in = new Scanner(System.in); // 输入金额,分为单位, 扩大100倍 int total = in.nextInt() * 100; in.nextLine(); int amount = in.nextInt(); int[] redPackage = new int[amount]; getRandomMoney(total, amount, redPackage); // 换做分输出 for (int i = 0; i < amount; i++) { System.out.println("抢到金额" + new BigDecimal(redPackage[i]).divide(new BigDecimal(100))); } } public static void getRandomMoney(int total, int amount, int[] redPackage) { int last_money = total; int last_people = amount; Random random = new Random(); for (int i = 0; i < amount - 1; i++) { //随机范围:[1,剩余人均金额的2倍 - 1] 分 int randRedValue = random.nextInt(last_money / last_people * 2 - 1) + 1; redPackage[i] = randRedValue; last_money -= randRedValue; last_people--; } //最后一人分剩余金额, 此处为非公平 redPackage[amount - 1] = last_money; } }

    3. 线段切割法

    前两种方法,并不能达到真正意义上的公平。而线段切割法,可以达到要求。

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

    如何确定每一条子线段的长度呢?由“切割点”来决定。当N个人一起抢红包的时候,就需要确定N-1个切割点。

    因此,当N个人一起抢总金额为M的红包时,我们需要做N-1次随机运算,以此确定N-1个切割点。随机的范围区间是(1, M)

    当所有切割点确定以后,子线段的长度也随之确定。这样每个人来抢红包的时候,只需要顺次领取与子线段长度等价的红包金额即可。

    这就是线段切割法的思路。在这里需要注意以下两点:

    当随机切割点出现重复,如何处理如何尽可能降低时间复杂度和空间复杂度 public class _03_RedPackage { //线段分割法 private static List<Integer> getRandomMoney(int totalMoney, int amount) { //验证参数合理校验 //为了使用random.nextInt(Integer)方法, 不得不先把红包金额放大100倍,最后在main函数里面再除以100 //这样就可以保证每个人抢到的金额都可以精确到小数点后两位 int redMoney = (int) (totalMoney * 100); if (redMoney < amount || redMoney < 1) { System.out.println("红包个数必须大于0,并且最小红包不少于1分"); } List<Integer> boards = new ArrayList<>(); boards.add(0); boards.add(redMoney); //红包个数和线段个数的关系 while (boards.size() <= amount) { // 随机生成切割点 int index = new Random().nextInt(redMoney - 1) + 1; if (boards.contains(index)) { //保证线段的位置不相同 continue; } boards.add(index); } //计算每个红包的金额,将两个板子之间的钱加起来 Collections.sort(boards); List<Integer> list = new ArrayList<>(); for (int i = 0; i < boards.size() - 1; i++) { Integer e = boards.get(i + 1) - boards.get(i); list.add(e); } return list; } public static void main(String[] args) { List<Integer> accountList = getRandomMoney(10, 5); BigDecimal count = new BigDecimal(0); for (Integer amount : accountList) { //将抢到的金额再除以100进行还原 BigDecimal tmpcount = new BigDecimal(amount).divide(new BigDecimal(100)); count = count.add(tmpcount); System.out.println("抢到金额:" + tmpcount); } System.out.println("total = " + count); } }

    【文章参考】

    漫画:如何实现抢红包算法?微信红包的随机算法是怎样实现的?面试题:如何实现红包算法微信红包算法以及带上下限的红包算法抢红包算法–四种抢红包算法对比
    Processed: 0.013, SQL: 8