題目
例如一個人在群里發了100塊錢的紅包,群里有10個人一起來搶紅包,每人搶到的金額隨機分配。
紅包功能需要滿足哪些具體規則呢?
1. 所有人搶到的金額之和要等於紅包金額,不能多也不能少。
2. 每個人至少搶到1分錢。
3. 要保證紅包拆分的金額盡可能分布均衡,不要出現兩極分化太嚴重的情況。
解決方案
解決方法一
思路
二倍均值法:假設剩余紅包金額為m元,剩余人數為n,那么有如下公式:
每次搶到的金額 = 隨機區間 [0.01,m /n × 2 - 0.01]元
這個公式,保證了每次隨機金額的平均值是相等的,不會因為搶紅包的先后順序而造成不公平。
舉個例子如下:
假設有5個人,紅包總額100元。100÷5×2 = 40,所以第1個人搶到的金額隨機范圍是[0.01,39.99]元,在正常情況下,平均可以搶到20元。假設第1個人隨機搶到了20元,那么剩余金額是80元。80÷4×2 = 40,所以第2個人搶到的金額的隨機范圍同樣是[0.01,
39.99]元,在正常的情況下,還是平均可以搶到20元。假設第2個人隨機搶到了20元,那么剩余金額是60元。60÷3×2 = 40,所以第3個人搶到的金額的隨機范圍同樣是[0.01,39.99]元,平均可以搶到20元。以此類推,每一次搶到金額隨機范圍的均值是相等的。
代碼實現
package arithmetic.com.ty.binary; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Random; public class RedEnvelope { /** * 拆分紅包 * * @param totalAmount 總金額(以分為單位) * @param totalPeopleNum 總人數 */ public static List<Integer> divideRedPackage(Integer totalAmount, Integer totalPeopleNum) { List<Integer> amountList = new ArrayList<Integer>(); Integer restAmount = totalAmount; Integer restPeopleNum = totalPeopleNum; Random random = new Random(); for (int i = 0; i < totalPeopleNum - 1; i++) { //隨機范圍:[1,剩余人均金額的2倍-1] 分 int amount = random.nextInt(restAmount / restPeopleNum * 2 - 1) + 1; restAmount = restAmount - amount; restPeopleNum--; amountList.add(amount); } amountList.add(restAmount); return amountList; } public static void main(String[] args) { List<Integer> amountList = divideRedPackage(1000, 10); for (Integer amount : amountList) { System.out.println(" 搶到金額:" + new BigDecimal(amount).divide(new BigDecimal(100))); } } }
缺點:這個方法雖然公平,但也存在局限性,即除最后一次外,其他每次搶到的金額都要小於剩余人均金額的2倍,並不是完全自由地隨機搶紅包。
解決方法二
思路
線段切割法:何謂線段切割法?我們可以把紅包總金額想象成一條很長的線段,而每個人搶到的金額,則是這條主線段所拆分出的若干子線段。

當N個人一起搶紅包的時候,就需要確定N-1個切割點。
因此,當N個人一起搶總金額為M的紅包時,我們需要做N-1次隨機運算,以此確定N-1個切割點。
隨機的范圍區間是(1, M)。當所有切割點確定以后,子線段的長度也隨之確定。這樣每個人來搶紅包的時候,只需要順次領取與子線段長度等價的紅包金額即可。
這就是線段切割法的思路。在這里需要注意以下兩點:
(1)當隨機切割點出現重復,如何處理 --- 重復了就重新切唄
(2)如何盡可能降低時間復雜度和空間復雜度 --- 這里我用鏈表,犧牲時間換取空間(排了個序),也可以犧牲空間節省時間(大數組)
代碼
package arithmetic.com.ty.binary; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; public class RedEnvelope { public static List<Integer> hongbao(int totalAmount, int totalNumber) { List<Integer> list = new ArrayList<>(); if (totalAmount <= 0 || totalNumber <= 0) { return list; } Set<Integer> set = new HashSet<>(); while (set.size() < totalNumber - 1) { //生成一個1~totalAmount的隨機數 int random = ThreadLocalRandom.current().nextInt(1, totalAmount); set.add(random); } //使用set.toArray(new Integer[0])是為了保證轉成數組后不用轉型。因為不帶Integer[0]的話,轉過后是Object[] Integer[] amounts = set.toArray(new Integer[0]); //排序之后首先把數組中的第一位數放入List中 Arrays.sort(amounts); list.add(amounts[0]); /** * 對排序后的數組進行如下操作。假如排序后的數組為{x1, x2, x3, x4, x5, x6} * 下面的規則就相當於是x2-x1+x3-x2+x4-x3+x5-x4+x6-x5=x6-x1。而x1已經在上面被添加到list中,因此現在list中數據總大小為x6。 * 因此最后list.add(totalAmount - amounts[amounts.length - 1])時,也就=list.add(totalAmount - x6),總數為totalAmount */ for (int i = 1; i < amounts.length; i++) { list.add(amounts[i] - amounts[i - 1]); } list.add(totalAmount - amounts[amounts.length - 1]); return list; } public static void main(String[] args) { List<Integer> list = hongbao(200, 20); System.out.println(list); System.out.println(list.stream().mapToInt(x -> x).sum()); } }
