最近在知乎看到关于抢红包分配方式的讨论,炒个冷饭,整理了一下高赞解决方案。
抢红包规则
-
抢到红包的所有人抢到的金额之和必须等于红包金额。
-
2.每个抢到的人至少分得一分钱。
-
3.所有人抢到金额的概率相等。
普通思路
方法
每次拆开红包,抢到的金额=(0,剩余金额)内的随机数。
问题
以这种方式分配,即以剩下金额随机的期望作为抢到金额。先抢到的人会有很大的优势,越往后的人抢到的金额越少,不满足条件3。例如:一个10个人共100元的红包,第一个人的随机区间为(0,100),他的红包期望是50。如果他真的抢到了50,第二个人的随机区间变为(0,50),他的红包期望为25。以此类推,后面的人抢到的金额会逐渐减少。
思路一 二倍均值法
方法
假设剩余红包金额为M,剩余人数为N,每次抢到的金额 = 随机区间 (0, M / N X 2)。
这个公式,保证了每次随机金额的平均值是相等的,不会因为抢红包的先后顺序而造成不公平。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static List<Integer> divideRedPackage(Integer totalAmount, Integer totalPeopleNum) { List<Integer> amountList = new ArrayList<Integer>(); Integer restAmount = totalAmount * 100; Integer restPeopleNum = totalPeopleNum; Random random = new Random(); for (int i = 0; i < totalPeopleNum - 1; i++) { int amount = random.nextInt(restAmount / restPeopleNum * 2 - 1) + 1; restAmount -= amount; restPeopleNum--; amountList.add(amount); } amountList.add(restAmount); return amountList; }
|
问题
除了最后一次,任何一次抢到的金额都不会超过人均金额的两倍,并不是任意的随机。
思路二 线段切割法
方法
当N个人一起抢总金额为M的红包时,我们需要做N-1次随机运算,以此确定N-1个切割点。
随机的范围区间是[1,100* M)。当所有切割点确定以后,子线段的长度也随之确定。这样每个人来抢红包的时候,只需要顺次领取与子线段长度等价的红包金额即可。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| private static List<Integer> divide(double money, int n) { int fen = (int) (money * 100); if (fen < n || n < 1) { System.out.println("红包个数必须大于0,并且最小红包不少于1分"); } List<Integer> boards = new ArrayList<>(); boards.add(0); boards.add(fen); while (boards.size() <= n) { int index = new Random().nextInt(fen - 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; }
|
问题
-
当随机切割点出现重复,如何处理。
-
如何尽可能降低时间复杂度和空间复杂度。