微信抢红包算法实现

字节跳动二面问到了这个问题,小小研究了一下。(ps:微信红包貌似是使用二倍均值法实现的)

微信抢红包算法

只讨论金额随机的情况,需要满足规则:

  1. 所有人抢到金额之和要等于红包总金额
  2. 每个人至少抢到一分钱
  3. 要保证所有人抢到金额的几率相等

方案一:每个人点进来领,金额随机,随机的上限是当前剩余的红包金额。每次抢到的金额 = 随机区间(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.0279628851027866541 个人:2.8 元
剩余金额:97.2 元
已被领取:2.8--------------------
ratio =  0.0096678677238389982 个人:0.97 元
剩余金额:96.23 元
已被领取:3.77--------------------
ratio =  0.111108282654751243 个人:11.11 元
剩余金额:85.12 元
已被领取:14.88--------------------
ratio =  0.06008120432817184 个人:6.01 元
剩余金额:79.11 元
已被领取:20.89--------------------
ratio =  0.378384809387495 个人:37.84 元
剩余金额:41.27 元
已被领取:58.73--------------------
ratio =  0.137301468526006146 个人:13.73 元
剩余金额:27.54 元
已被领取:72.46--------------------
ratio =  0.15924261331372837 个人:15.92 元
剩余金额:11.62 元
已被领取:88.38--------------------
ratio =  0.091947204530332378 个人:9.19 元
剩余金额:2.43 元
已被领取:97.57--------------------
ratio =  0.0083975622132458979 个人:0.84 元
剩余金额:1.59 元
已被领取:98.41--------------------10 个人:1.59 元
剩余金额:0.0 元
已被领取:100.0--------------------

你可能感兴趣的:(碎碎念)