banner
约 3,000 字
10 分钟

为什么越后抢的人越容易手气最佳?逆向微信红包分配算法

2026年2月21日
2026年2月25日

摘要

微信红包的分配算法是一个复杂的问题,研究人员通过统计学视角和逆向工程来分析其规律。研究发现,微信红包的分配过程是动态的,每次发红包时,微信后台都会根据当前剩余人数和剩余金额计算出下一次应该发多少钱。研究人员推导出两个结论:每个人分得的红包金额的数学期望是相等的,越晚抢红包的人,分得红包金额的方差越大。然而,这个算法存在问题,例如钱是整数不是分数,程序猿涉及金钱业务的大忌。为了解决这个问题,研究人员提出三种不同的分配算法,并通过蒙特卡罗模拟来比较这些算法的表现。结果表明,算法四的表现最好,但仍然没有完美复现微信红包算法。研究人员还回答了两个问题:台球店老板在粉丝群发红包,手气最佳者可以免台费,是否存在最佳策略?答案是越后抢越好。酸穷

什么,抢红包也居然存在最优策略?这篇文章研究了统计学视角下「手气王」的分布规律,以及微信红包分配算法的逆向。为了珍贵的研究数据,楼主派发了约 2000 个红包,总耗时 3 个月。


在开启分析之旅前,大伙不妨先思考两个问题:

台球店老板在粉丝群发红包,手气最佳者可以免台费,是否存在最佳策略

酸穷大学生在分配小组任务的时候,采用红包抽签方案。发1毛钱小金额红包,每个人都只能拿几分钱,这样的抽签是否公平

9cfc143d112b09af245f0149debd59c0_720.jpg
9cfc143d112b09af245f0149debd59c0_720.jpg

换言之,微信红包的分配是否是公平的?先抢和后抢有没有区别?手气王到底服从什么样的分布规律?要回答这些问题,我们必须分析出微信红包的分配算法。

幸运的是,我们可以站在前人的肩膀上。最早透露微信红包算法的是当时的支付后台架构负责人方乐明[^1],后来也有网友在此基础上分析它的具体算法[^2][^5],工程实现[^3][^4]等。这些文章都提到了一个基础算法——二倍均值法

微信红包的分配过程是动态的,也就是说每发出去一枚红包,微信后台就会根据 当前剩余人数剩余金额 计算出下一次应该发多少钱,然后依次类推,直到红包发完为止。具体每次发多少钱,是从 0 到 剩余金额➗剩余人数✖️2 这个区间内随机抽取的。

知道了这个算法,我们可以推导出两个结论

  1. 每个人分得的红包金额的数学期望是相等的:换言之,微信红包还算公平(话说要是平均每个人拿到的钱不相等那还像话么)

  2. 越晚抢红包的人,分得红包金额的方差越大:这就涉及到今天讨论的手气王问题了。手气王是什么,是极值呀。极值一般发生在波动较大的地方,也就是我们可以认定,越晚抢的人,越容易拿到手气王

下面做一个简单的数学证明(WARNING:下面大量公式出没,非战斗人员可跳过,直接看算法实现部分)

设红包总金额为 MM,总人数为 NNXkX_k 为第 kk 个人抢到的金额,RkR_k 为第 kk 个人抢红包之前的剩余金额,nkn_k 为剩余人数。于是

R1=M,Rk+1=RkXknk=Nk+1\begin{align} \\ &R_{1}=M,R_{k+1}=R_{k}-X_{k} \\ &n_{k}=N-k+1 \end{align}

对于第 kk 个人,XkX_{k} 服从如下均匀分布:

XkRkU(0,Rknk×2)X_{k}|R_{k} \sim U\left( 0, \frac{R_{k}}{n_{k}} \times 2 \right)

使用数学归纳法证明结论一:

对于第1个人,XkU(0,MN×2)X_{k} \sim U\left( 0, \frac{M}{N}\times 2 \right),可以得到第一个人的期望

E[X1]=0+2MN2=MN\mathbb{E}[X_{1}]=\frac{ 0+\frac{2M}{N}}{2}=\frac{M}{N}

假设对于所有 j<kj<k,都有 E[Xj]=MN\mathbb{E}[X_{j}]= \frac{M}{N}。根据全期望公式

E[Xk]=E[E[XkRk]]=E[12×2RkNk+1]=E[RkNk+1]=E[Rk]Nk+1\begin{align} \\ \mathbb{E}[X_{k}]&=\mathbb{E}[\mathbb{E}[X_{k}|R_{k}]] \\ &=\mathbb{E}\left[ \frac{1}{2}\times \frac{2R_{k}}{N-k+1} \right] \\ &=\mathbb{E}\left[ \frac{R_{k}}{N-k+1} \right] \\ &=\frac{\mathbb{E}[R_{k}]}{N-k+1} \end{align}

而剩余金额 RkR_{k} 是总金额减前面 k1k-1个人拿去的钱:

Rk=Mj=1k1XjR_{k}=M-\sum_{j=1}^{k-1}X_{j}

代入上式,可得:

E[Xk]=1Nk+1E[Mj=1k1Xj]=1Nk+1(Mj=1k1E[Xj])=1Nk+1(M(k1)MN)=Nk+1Nk+1MN=MN\begin{align} \\ \mathbb{E}[X_{k}]&=\frac{1}{N-k+1}\mathbb{E}\left[ M-\sum_{j=1}^{k-1}X_{j} \right] \\ &=\frac{1}{N-k+1}\left( M-\sum_{j=1}^{k-1}\mathbb{E}[X_{j}] \right) \\ &=\frac{1}{N-k+1}\left( M-(k-1) \frac{M}{N} \right) \\ &=\frac{\cancel{ N-k+1 }}{\cancel{ N-k+1 }} \cdot \frac{M}{N}=\frac{M}{N} \end{align}

证明结论二: 上文已经证明了期望相等,那么根据方差公式 Var(X)=E[X2]E[X]2Var(X)=\mathbb{E}[X^{2}]-\mathbb{E}[X]^{2},只需要证明二阶矩 E[Xk+12]>E[Xk2]\mathbb{E}[X_{k+1}^{2}]>\mathbb{E}[X_{k}^{2}] 即可。

根据定义,XkU(0,2Rknk)X_{k} \sim U\left( 0, \frac{2R_{k}}{n_{k}} \right),直接用均匀分布的期望平方公式,可得

E[Xk2Rk]=13(2Rknk)2=43nk2Rk2\mathbb{E}[X_{k}^{2}|R_{k}]=\frac{1}{3}\left( \frac{2R_{k}}{n_{k}} \right)^{2}=\frac{4}{3n_{k}^{2}}R_{k}^{2}

两边同时求期望:(全期望公式)

E[Xk2]=43nk2E[Rk2]    E[Xk2]E[Rk2]=43nk2(1)\mathbb{E}[X_{k}^{2}]=\frac{4}{3n_{k}^{2}}\mathbb{E}[R_{k}^{2}]\implies \frac{\mathbb{E}[X_{k}^{2}]}{\mathbb{E}[R_{k}^{2}]}=\frac{4}{3n_{k}^{2}}\tag{1}

接下来是 Rk+1R_{k+1}RkR_{k} 之间的递推关系: 根据定义:

Rk+1=RkXk    Rk+12=Rk22RkXk+Xk2    E[Rk+12Rk]=Rk22RkE[XkRk]+E[Xk2Rk]\begin{align} \\ R_{k+1}&=R_{k}-X_{k} \\ \implies R_{k+1}^{2} &= R_{k}^{2}-2R_{k}X_{k}+X_{k}^{2} \\ \implies \mathbb{E}[R_{k+1}^{2}|R_{k}]&=R_{k}^{2}-2R_{k}\mathbb{E}[X_{k}|R_{k}]+\mathbb{E}[X_{k}^{2}|R_{k}] \end{align}

把上文的结果 E[XkRk]=Rknk,E[Xk2Rk]=4Rk23nk2\mathbb{E}[X_{k}|R_{k}]=\frac{R_{k}}{n_{k}},\mathbb{E}[X_{k}^{2}|R_{k}]=\frac{4R_{k}^{2}}{3n_{k}^{2}}代入得到:

E[Rk+12Rk]=Rk22Rk(RkNk)+4Rk23nj2=Rk2(12nk+42nk2)=Rk23nk26nk+43nk2    E[Rk+12]=E[Rk2]3nk26nk+43nk2\begin{align} \\ \mathbb{E}[R_{k+1}^{2}|R_{k}]&=R_{k}^{2}-2R_{k}\left( \frac{R_{k}}{N_{k}}\right)+\frac{4R_{k}^{2}}{3n_{j}^{2}} \\ &=R_{k}^{2}\left( 1-\frac{2}{n_{k}}+\frac{4}{2n_{k}^{2}} \right) \\ &= R_{k}^{2}\cdot \frac{3n_{k}^{2}-6n_{k}+4}{3n_{k}^{2}} \\ \implies \mathbb{E}[R_{k+1}^{2}]&=\mathbb{E}[R_{k}^{2}] \cdot \frac{3n_{k}^{2}-6n_{k}+4}{3n_{k}^{2}} \tag{2} \end{align}

所以我们由 (1),(2) 可以推出

E[Xk+12]=43(nk1)2E[Rk+12]=43(nk1)2E[Rk2]3nk26nk+43nk2    E[Xk+12]E[Xk2]=43(nk1)2E[Rk2]3nk26nk+43nk243nk2E[Rk2]=3nk26nk+43nk26nk+3>1\begin{align} \\ \mathbb{E}[X_{k+1}^{2}]&=\frac{4}{3(n_{k}-1)^{2}}\mathbb{E}[R_{k+1}^{2}] \\ &=\frac{4}{3(n_{k}-1)^{2}}\cdot \mathbb{E}[R_{k}^{2}] \cdot \frac{3n_{k}^{2}-6n_{k}+4}{3n_{k}^{2}} \\ \implies \frac{\mathbb{E}[X_{k+1}^{2}]}{\mathbb{E}[X_{k}^{2}]}&= \frac{\frac{4}{3(n_{k}-1)^{2}}\cdot \cancel{ \mathbb{E}[R_{k}^{2} }] \cdot \frac{3n_{k}^{2}-6n_{k}+4}{3n_{k}^{2}} }{\frac{4}{3n_{k}^{2}}\cancel{ \mathbb{E}[R_{k}^{2}] }} \\ &=\frac{3n_{k}^{2}-6n_{k}+4}{3n_{k}^{2}-6n_{k}+3}>1 \end{align}

所以 E[Xk+1]2>E[Xk2]    Var(Xk+1)>Var(Xk)\mathbb{E}[X_{k+1}]^{2}>\mathbb{E}[X_{k}^{2}] \iff Var(X_{k+1})>Var(X_{k}) ,证毕。


各位看官可能要被绕晕了,不过不要紧,证明不考(狗头

能不能再给力一点啊老师.jpg
能不能再给力一点啊老师.jpg

同学,真正的逆向才刚刚开始......

话说数学很单纯,但业务很复杂。要是微信真按这个算法来,恐怕要被用户按在地上问候了(虽然现在也是这样)这个算法的问题出在哪里呢?

  • 钱是整数不是分数,程序猿涉及金钱业务的大忌。千万把离散的钱变成连续的,不然用户会看着 1.14514 元陷入沉思。

  • 既然钱是离散的,那么恐怕就不能从 0 开始取吧,否则用户会开出个 0 元的红包。

  • 假如 2 分钱的红包分给 2 个人。按算法第一个人可以拿到 22×2=2\frac{2}{2}\times 2=2 分钱,那么最后一个人呢?没有什么限制的话,只能喝西北风了。

既然微信针对离散的情形做了优化,那么究竟是什么样的呢?是先用连续抽取再变成离散,还是从一开始就是离散抽取?为了保证每个人抽取至少有1分钱,微信是怎么做的?

很遗憾,这个模型到现在还是黑盒状态。但是我们可以从一个神奇的现象中管中窥豹。

这个现象就是——末位红包抽屉原理[^6]。

咳咳,这名字不是我取的,来源于@毕导 (顺便一提这篇公众号文章写得很棒,基本上把大金额情况下的结论解释清楚了,推荐大家都观摩一下)

末位红包抽屉原理指的是,当 n+1n+1 分钱的红包被 nn 个人抢时,永远是最后一位拿到 2 分钱,而前面的人通通都是 1 分钱。也就是说永远是最后一位手气最佳。

Pasted image 20260220215713.png
Pasted image 20260220215713.png

诸位如果不信的话,可以拿亲朋好友测试一下,保证你可以拿手气王:

Pasted image 20260220210449.png
Pasted image 20260220210449.png

诶?楼主你不是说,每个人拿到钱的期望是一样的么,怎么到这里就是错的了呢?

这是因为,刚刚讨论的是连续的情况,而这里金额单位是,也就是离散的情况。这便是问题的复杂和有趣之处了——在离散的情况下,刚刚推导的结论完全不成立,需要重新建立一个算法。

能不能再给力一点啊老师.jpg
能不能再给力一点啊老师.jpg

为了彻底搞清楚微信是怎么发红包的,楼主打入了某红薯平台,潜入了十数个跨年红包群,然后在里面嘎嘎发红包,在小妹妹面前当金主爸爸(划掉)。

Pasted image 20260220211514.png
Pasted image 20260220211514.png

(顺便提一嘴,期间还遇到了两个被微信封杀的诈骗群。红包群只要是收入群费的,一律鉴定为诈骗......)

然后楼主派发了 200 个 10 人份的红包(金额0.19元),共计发了约 2000 份红包。我敢说,这200 份数据是全互联网最大的一组微信红包数据集,没有之一。

Pasted image 20260220212113.png
Pasted image 20260220212113.png

我们来画出 200 次抢红包中,手气王是如何分布的:

Pasted image 20260220214630.png
Pasted image 20260220214630.png

非常的 Amazing 啊,第一个抢的同学居然没有一次是手气王!更加神奇的是,最后一位抢的包揽了几乎一半的手气王,这是不是有些太夸张了点?没错,这就是小金额微信红包的现状。小金额的情况下,后抢优势被远远放大了。

而且注意到,第一位永远是 0.01 元。但根据二倍均值法,应该取得到 0.1910×2=0.02\lfloor \frac{0.19}{10}\rfloor\times 2=0.02 元才对呀,这是为什么呢?

我们有理由相信,微信为了保证下一位至少有 0.01 元,针对随机上界作出了调整。于是我们可以立即写出三种不同的分配算法:

Python
def split_red_envelope(
    total_yuan: float, n: int, seed: Optional[int] = None
) -> List[float]:
    total_cents = int(round(total_yuan * 100))
    rng = random.Random(seed)
    rem = total_cents
    amounts = []

    for k in range(n, 0, -1):
        if k == 1:
            amount = rem
        else:
            # 原汁原味版算法
            ub = math.floor(rem / k) * 2
            # 第一种:上限减一分钱,保证后一个至少有1分钱拿
            ub = math.floor(rem / k) * 2 - 1
            # 第二种:两个上限做最小值,也可以保证后一个有1分钱
            ub = math.floor(rem / k) * 2
            ub = min(ub, rem - (k - 1) * 1)
            # 第三种:改变取整的位置,因为你不知道微信的真实算法是什么,所以啥都要尝试一下
            ub = math.floor((rem / k) * 2)
            ub = min(ub, rem - (k - 1) * 1)

            amount = rng.randint(1, ub)
        amounts.append(amount)
        rem -= amount

    return [round(c / 100.0, 2) for c in amounts]

这三种算法都符合「末位红包抽屉原理」,那应该如何鉴别算法之间的好坏呢?对于每个算法,我们可以跑 2000 次蒙特卡罗模拟,计算出模拟情况下的手气分布。下面是这些算法和真实分布的比较:

原版算法
原版算法
算法一
算法一

算法二
算法二
算法三
算法三

可以看出,原版算法错得相当离谱。算法二、算法三都不符合柱状图的基本规律。只有算法一勉强能看……但是也是错的,因为根本通过不了卡方检验。

于是我思考了一个问题,这个抽取,真的是均匀的吗?

下面是一张数据透视表,表头是拿到的金额(分),第 n 行是顺序为 n 的人拿到的金额。

Pasted image 20260221010339.png
Pasted image 20260221010339.png

可以看到,第一位全部都只拿到 1 分是没有问题的,但是第二位理论上拿1、2、3分钱的概率相同,但是居然呈现了 2:1:1 的趋势,这样的结果已经不能用统计误差来解释了。也就是说,我们之前默认的均匀抽取这个大前提被推翻了。

这样的不均匀抽取,是如何产生的呢?我猜测,微信在做抽取的时候是从0开始的连续均匀抽样,抽取的数字小于1时,会自动变为1。这也就解释了为什么恰好是2:1:1

手搓的示意图
手搓的示意图

嗯,很合理,于是算法四诞生了:

Python
def split_red_envelope(
    total_yuan: float, n: int, seed: Optional[int] = None
) -> List[float]:
    total_cents = int(round(total_yuan * 100))
    rng = random.Random(seed)
    rem = total_cents
    amounts = []

    for k in range(n, 0, -1):
        if k == 1:
            amount = rem
        else:
            ub = math.floor(rem / k) * 2
            ub = min(ub, rem - (k - 1))
            # 算法四:连续抽取+判断数字是否合法
            amount = rng.uniform(0, ub)
            amount = max(1, amount)
            amount = math.floor(amount)

        amounts.append(amount)
        rem -= amount

我们来看看算法四的表现:

算法四
算法四

已经和真实数据差异不大了,下降随后上升的趋势也复现得很完美。

能不能再给力一点啊老师.jpg
能不能再给力一点啊老师.jpg

如你所见,微信红包算法并没有被完美复现。因为当你做一个卡方拟合优度检验的时候,会发现真实算法长这样,概率只有5.5%。(恰好不能被拒绝,嘿嘿嘿)

X^2

自由度

p-value

15.211

8

0.05516

虽然没有完美复现,但至少也算是探索了一把微信红包的底层机制。还要再进一步复原的话,就只能问张小龙了

Pasted image 20260221002634.png
Pasted image 20260221002634.png

回答一下开头提的问题:

Q:台球店老板在粉丝群发红包,手气最佳者可以免台费,是否存在最佳策略? A:当然有,越后抢越好。最好把人数和金额都在电脑里跑一遍,甚至可以确定在第几位抢最合适(前提是你手稳得一批)

Q:酸穷大学生在分配小组任务的时候,采用红包抽签方案。发1毛钱小金额红包,每个人都只能拿几分钱,这样的抽签是否公平? A:不公平,越后抢的人越容易手气最佳。最好使用 Python 或者 R 跑个随机数,这个是最公平的。

最后祝大伙们马年吉祥,财源滚滚!

30b6c043db6463c1d4a3129b306aba64_720.jpg
30b6c043db6463c1d4a3129b306aba64_720.jpg

[^1]: 揭秘微信红包:架构、抢红包算法、高并发和降级方案 https://www.cnblogs.com/8hao/p/5383143.html 微信红包金额分配的算法 https://timyang.net/architecture/wechat-red-packet/ 微信红包实现原理 https://www.phppan.com/2015/04/weixin-hongbao/ 微信红包的架构设计简介 https://www.zybuluo.com/yulin718/note/93148 微信红包的随机算法是怎样实现的? - 沉简的回答 https://www.zhihu.com/question/22625187/answer/85530416 微信红包先抢和后抢差距居然这么大!春节抢红包的大数据分析 https://mp.weixin.qq.com/s/5YV4QsK6zXzx-_CdaeLutA

END

相关文章

暂无相关文章

© 2026 东瓜瓜. All Rights Reserved. / RSS / Sitemap
Powered by Tanstack Start & Flare Stack Blog