微信红包算法原来是这样

1,321 阅读4分钟

前言

在微信抢红包时,总有人抱怨自己运气差,抢到的红包金额小得可怜,而别人却能抢到大额红包。其实这背后并不是单纯的运气问题,而是隐藏着精妙的算法逻辑。

图片

微信红包算法的核心原理

微信红包采用的是一种名为 "二倍均值法" 的算法,它巧妙地平衡了随机性和公平性。这种算法的核心思想是:每次可抢金额的上限是当前剩余人均金额的两倍。具体来说,假设总金额为 M 元,参与人数为 N 人,那么平均每人可得金额为 M/N 元。在抢红包时,系统会随机生成一个介于 0 到 2*(M/N) 之间的金额作为当前抢到的红包金额,然后从总金额中扣除这个金额,剩余的金额继续按照同样的规则分配给下一个人,直到最后一个红包直接取剩余金额。

这种算法既保证了每个人都有机会抢到红包,又避免了前面的人把大部分金额抢走的情况。不过,由于最后一个人的红包金额是固定的,所以抢红包的顺序确实会对结果产生一定影响。

JavaScript 实现微信红包算法

以下是微信红包算法的 JavaScript 实现代码:

/**
 * 微信红包算法实现
 * @param {number} total 总金额(元)
 * @param {number} num 红包个数
 * @return {number[]} 红包金额数组
 */
function wechatRedPacket(total, num) {
  const packets = []; // 存储红包金额
  let restAmount = total; // 剩余金额
  let restNum = num; // 剩余红包个数

  // 分配前 n-1 个红包
  for (let i = 0; i < num - 1; i++) {
    // 计算当前最大可抢金额:二倍均值
    const max = (restAmount / restNum) * 2;

    // 生成随机金额,保留两位小数
    const amount = parseFloat((Math.random() * max).toFixed(2));

    // 保证最小金额 0.01 元
    const finalAmount = Math.max(amount, 0.01);

    restAmount -= finalAmount;
    restNum--;
    packets.push(finalAmount);
  }

  // 最后一个红包直接取剩余金额
  packets.push(parseFloat(restAmount.toFixed(2)));

  return packets;
}

// 示例:100 元分给 10 个人
const redPackets = wechatRedPacket(100, 10);
console.log(redPackets);
// 输出示例:[15.20, 8.45, 12.31, ... , 9.87]

为什么你总抢到几分钱?

根据二倍均值法,我们来分析一下为什么你总抢到几分钱:

数学原理分析

设当前剩余金额为 M,剩余人数为 N:

  • • 当前平均值 = M/N
  • • 随机范围 = [0, 2M/N]
  • • 金额期望值 = (0 + 2M/N)/2 = M/N

这意味着每个人的期望收益相同,但由于以下心理和技术因素,现实感受却不同:

  • • 记忆偏差 :大脑更容易记住极端情况,比如抢到超大或超小红包。
  • • 样本不足 :个人抢红包次数有限,无法反映真实概率分布。
  • • 位置效应 :后期参与者面临更小的随机范围。
  • • 心理暗示 :“运气差”的自我暗示会强化负面记忆。

抢红包策略建议

  • • 当群人数少时,早抢有机会抢到大额红包。
  • • 当群人数多时,晚抢避免抢到几分钱,金额更稳定。

大厂面试考点解析

微信红包算法是面试中的经典题目,以下是常见考点:

算法优化

为解决浮点数精度问题,可以将金额转换为分进行计算:

// 优化版:解决浮点数精度问题
function optimizedRedPacket(total, num) {
  // 转为分计算,避免浮点误差
  let restAmount = total * 100;
  const packets = [];

  for (let i = 0; i < num - 1; i++) {
    const max = Math.floor((restAmount / (num - i)) * 2);
    const amount = Math.floor(Math.random() * max);
    // 确保至少 1 分钱
    const finalAmount = Math.max(amount, 1);
    restAmount -= finalAmount;
    packets.push(finalAmount / 100);
  }
  packets.push(restAmount / 100);
  return packets;
}

面试常见问题

  • • 如何证明算法的公平性?
    • • 通过期望值计算:E = M/N
    • • 方差分析:Var = (M²)/(3N²)
  • • 边界情况处理
    • • 最小金额保证
    • • 最后一人金额处理
    • • 总金额不足时的处理
  • • 并发场景设计
    • • 使用数据库事务保证金额一致性
    • • Redis 分布式锁防止超抢
    • • 预生成红包序列减少实时计算

扩展思考

  • • 如何设计红包金额的正态分布?
  • • 怎样实现 “手气最佳” 动画效果?
  • • 如何防止红包作弊行为?