字节跳动二面问到了这个问题,小小研究了一下。(ps:微信红包貌似是使用二倍均值法实现的)
只讨论金额随机的情况,需要满足规则:
方案一:每个人点进来领,金额随机,随机的上限是当前剩余的红包金额。每次抢到的金额 = 随机区间(0,剩余红包金额)
分析:这样做的缺陷是越早领越有优势,因为每次抢到的金额 = 随机区间(0,剩余金额),越早抢能领到的平均金额越大。假设有 10 个人,红包总金额 100,第一个人的随机范围是(0,100),平均金额 = 50;假设第一个人随机到 50 元,第二个人的随机范围就是(0,50),平均金额 = 25;假设第二个人随机到 25 元,第三个人的随机范围就是(0,25),平均金额 = 12.5;以此类推,每一次的随机范围越来越小,平均金额也越小。
方案二:二倍均值法。设剩余红包金额为 M,剩余人数为 N,每次抢到的金额 = 随机区间(0,M / N * 2)。
分析:这样保证了每个随机金额的平均值是相等的,不会因为抢红包的先后顺序而造成不公平。假设有 10 个人,红包总金额 100,第一个人的随机范围是(0,100/10 * 2)=(0,20),平均金额 = 10;假设第一个人随机到 10 元,第二个人的随机范围就是(0,90/9 * 2)=(0,20),平均金额 = 10;假设第二个人随机到 10 元,第三个人的随机范围就是(0,80/8 * 2)=(0,20),平均金额 = 10。以此类推,每一次的随机范围都相同,平均值也相同。二倍均值法保证了抢红包的公平性,但不能保证真正的随机性。因为除了最后一个人,前面任何一个人抢到的金额都一定小于当前人均金额的两倍,并不是真正的随机。
Python 代码实现:
import random
def getPacket(amount, nums):
limit = round(amount / nums * 2, 2) # 求取上限(0,M / N * 2)
print('随机金额范围:({}, {})'.format(0, limit))
cur = round(random.uniform(0, limit), 2)
return cur
if __name__ == '__main__':
amount, nums = 100, 10
cur_num = nums
send = 0
for i in range(cur_num):
if i == nums-1:
reward = amount
else:
reward = getPacket(amount, cur_num)
amount = round(amount - reward, 2)
send = round(send + reward, 2)
cur_num -= 1
print('第 {} 个人:{} 元'.format(i+1, reward))
print('剩余金额:{} 元'.format(amount))
print('已被领取:{} 元'.format(send))
print('--------------------')
>>>
随机金额范围:(0, 20.0)
第 1 个人:0.32 元
剩余金额:99.68 元
已被领取:0.32 元
--------------------
随机金额范围:(0, 22.15)
第 2 个人:20.5 元
剩余金额:79.18 元
已被领取:20.82 元
--------------------
随机金额范围:(0, 19.8)
第 3 个人:8.3 元
剩余金额:70.88 元
已被领取:29.12 元
--------------------
随机金额范围:(0, 20.25)
第 4 个人:10.25 元
剩余金额:60.63 元
已被领取:39.37 元
--------------------
随机金额范围:(0, 20.21)
第 5 个人:7.01 元
剩余金额:53.62 元
已被领取:46.38 元
--------------------
随机金额范围:(0, 21.45)
第 6 个人:11.42 元
剩余金额:42.2 元
已被领取:57.8 元
--------------------
随机金额范围:(0, 21.1)
第 7 个人:15.78 元
剩余金额:26.42 元
已被领取:73.58 元
--------------------
随机金额范围:(0, 17.61)
第 8 个人:0.9 元
剩余金额:25.52 元
已被领取:74.48 元
--------------------
随机金额范围:(0, 25.52)
第 9 个人:8.11 元
剩余金额:17.41 元
已被领取:82.59 元
--------------------
第 10 个人:17.41 元
剩余金额:0.0 元
已被领取:100.0 元
--------------------
方案三:生成 k 个随机数,k 等于设置的分红包人数,保证 k 个随机数的和为 1,用总金额分别去乘这 k 个比例值,即可得到随机金额的 k 个红包,保证公平性和随机性。
import random
def getPacketRatio(nums):
rand = [random.uniform(0, 5) for _ in range(nums)]
sum = 0
for i in range(nums):
sum += rand[i]
for i in range(nums):
rand[i] = rand[i] / sum
return rand
if __name__ == '__main__':
amount, nums = 100, 10
cur_amount, cur_num = amount, nums
ratio = getPacketRatio(nums)
#print(sum(ratio))
send = 0
for i in range(cur_num):
if i == nums-1:
reward = cur_amount
else:
#print('ratio = ', ratio[i])
reward = round(amount * ratio[i], 2)
cur_amount = round(cur_amount - reward, 2)
send = round(send + reward, 2)
print('第 {} 个人:{} 元'.format(i+1, reward))
print('剩余金额:{} 元'.format(cur_amount))
print('已被领取:{} 元'.format(send))
print('--------------------')
>>>
ratio = 0.027962885102786654
第 1 个人:2.8 元
剩余金额:97.2 元
已被领取:2.8 元
--------------------
ratio = 0.009667867723838998
第 2 个人:0.97 元
剩余金额:96.23 元
已被领取:3.77 元
--------------------
ratio = 0.11110828265475124
第 3 个人:11.11 元
剩余金额:85.12 元
已被领取:14.88 元
--------------------
ratio = 0.0600812043281718
第 4 个人:6.01 元
剩余金额:79.11 元
已被领取:20.89 元
--------------------
ratio = 0.37838480938749
第 5 个人:37.84 元
剩余金额:41.27 元
已被领取:58.73 元
--------------------
ratio = 0.13730146852600614
第 6 个人:13.73 元
剩余金额:27.54 元
已被领取:72.46 元
--------------------
ratio = 0.1592426133137283
第 7 个人:15.92 元
剩余金额:11.62 元
已被领取:88.38 元
--------------------
ratio = 0.09194720453033237
第 8 个人:9.19 元
剩余金额:2.43 元
已被领取:97.57 元
--------------------
ratio = 0.008397562213245897
第 9 个人:0.84 元
剩余金额:1.59 元
已被领取:98.41 元
--------------------
第 10 个人:1.59 元
剩余金额:0.0 元
已被领取:100.0 元
--------------------