规则:
- 所有人抢到金额之和等于红包金额,不能超过,也不能少于
- 每个人至少抢到一分钱
- 要保证所有人抢到金额的几率相等
一、二倍均值法
剩余红包金额M,剩余人数N,那么:每次抢到金额=随机(0,M/N2)
保证了每次随机金额的平均值是公平的
假设10人,红包金额100元
第一人:100/102=20,随机范围(0,20),平均可以抢到10元
第二人:90/92=20,随机范围(0,20),平均可以抢到10元
第三人:80/82=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;
}