红包算法[切西瓜法] | 青训营笔记
这是我参与「第四届青训营」笔记创作活动的的第七天。参加青训营第五天的时候,老师讲了一下红包算法。当时以为自己已经记住了,过了两天发现已经基本上不记得老师在课堂上讲了什么内容了。所以特意写下这篇文章,帮助自己记忆。
我们用代码将一笔钱放在多个红包里,这个问题看似很简单,不就是把钱塞到红包里嘛。但实际写起代码来就发现问题了。假设100块钱塞到10个红包里,红包的最小金额为0.01元。如果只是随机的把100块钱分成10份的话,就有可能出现有一个红包里塞了99块钱,然后其他9个红包就只剩下1块钱可以分配了,这样的红包,领到99块的人肯定很高兴,但是剩下的9个人加在一起也只能分到1块钱,这9个人肯定是不高兴的。并且如果只是随机的将钱塞到红包里的话,还有可能出现,分到最后面,剩余的金额小于0.01元了,导致红包无法正常塞入金额。那我们该如何保证这100块钱能比较均匀的塞到红包里且刚好能塞到10个红包里呢?
切西瓜算法
就像这个算法的名字一样,切西瓜。将红包总金额看作是一个“大西瓜”,我们先将西瓜一劈为二,然后选择两块西瓜中较大的一半,在一劈为二,循环往复,直到得到我们想要的西瓜块数。
- 将需要劈开的100块钱放入一个数组中
- 选择数组中最大的数字,将其劈开。使用这个最大数字除以2之后乘上一个0-1之间的随机数然后加1得到一个随机的一半大小,它可能是大的一半也可能是小的一半,就像我们蒙上眼睛劈西瓜一样,我们不能确定哪一块西瓜更大,两半西瓜的大小的随机的。
- 用被劈的金额减去劈出来的金额,得到另一半劈出来的金额。
- 将这个被劈的金额从数组中删除,并将劈出来的两个金额加入到数组中。
- 继续从数组中选择最大的数字,重复以上操作
下面让我们直接来看代码。
function generate(amount, count){
let ret = [amount];
while(count > 1){
//找到最大一块来切分
let cake = Math.max(...ret),
idx = ret.indexOf(cake),
part = 1 + Math.floor((cake / 2) * Math.random()),
rest = cake - part;
ret.splice(idx, 1, part, rest);
count--;
}
return ret;
}
console.log(generate(100,10))
上面代码运行结果为
[ 7, 12, 13, 7, 18, 6, 9, 6, 3, 19 ]。一百块钱拆成10个红包。
这劈西瓜似的分红包方法,保证了金额能比较均匀的分配到每个红包中去。