一、前言
看小灰公众号有个文章,如何实现抢红包算法,很感兴趣,便跟着研究了一下。
如一个红包100元,分为5个人去抢,我自己脑海中首先浮现的就是挨个取随机数就行了呗,即 第一个随机范围为(0,100),值为i,第二个的随机范围就变为(0,100-i),以此类推即可。但这样就会暴露出几个较大的问题,即红包分配及其不均匀,并且前面拿到大红包的概率会更大。
因此公众号中出了另外两个方案。1. 二倍均值法;2. 线段切割法
二、方法
1. 二倍均值法
剩余红包金额为M,剩余人数为N,那么有如下公式:
每次抢到的金额 = 随机区间 (0, M / N X 2)。如果为100红包5个人去抢
每次每个人可以抢到的期望均为20;实现如下:
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,剩余人均金额的两倍),左闭右开
int amount = random.nextInt(restAmount / restPeopleNum * 2 - 1) + 1;
restAmount -= amount;
restPeopleNum--;
amountList.add(amount);
}
amountList.add(restAmount);
return amountList;
}
public static void main(String[] args) {
List<Integer> amountList = divideRedPackageNew(1000, 5);
for (Integer amount : amountList) {
System.out.println("抢到金额:" + new BigDecimal(amount).divide(new BigDecimal(100)));
}
}
2. 线段切割法
线段切割法:我们可以把红包总金额想象成一条很长的线段,而每个人抢到的金额,则是这条主线段所拆分出的若干子线段。如下图所示,100红包分五个人,即需要四个随机的“线段”:
上面的方法看似来说很“科学”,但真的那么好吗,如果发生了线段切割点的碰撞怎么办?
公众号中给的方式是,直接给当前线段+1或-1一直到没有碰撞位置。这样就会导致如果一旦发生了碰撞,就会出现一个0.01的红包,这样好吗?
我当前的解法也不够好,如果碰撞就继续随机,直到随机到为止,我的解法也非常不好,因为如果给的是0.06元,5个人的红包,这样的碰撞机率便会很高,导致整个算法很有可能会一直迭代下去,出现大问题;
我当前的解法代码如下:请大家慎用。。。因为如果碰撞率高的话,真的会有很大的效率问题,所以当前临时的解决方案就还是给碰撞端+1或-1直至不碰撞为止。
public static List<Integer> divideRedPackageNew(Integer totalAmount, Integer totalPeopleNum) {
//人数比钱数多则直接返回错误
if(totalAmount<totalPeopleNum){
System.out.println("钱数人数设置错误!");
return null;
}
//随机分割totalPeopleNum-1个点
List<Integer> indexList = new ArrayList<>();
List<Integer> amountList = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < totalPeopleNum - 1; i++) {
int index;
{
index = random.nextInt(totalAmount - 2) + 1;
//如果出现碰撞怎么办
}
while (indexList.contains(index));
indexList.add(index);
}
Collections.sort(indexList);
int start = 0;
for (Integer index:indexList) {
amountList.add(index-start);
start = index;
}
amountList.add(totalAmount-start);
return amountList;
}