🎉红包算法的秘密:从“手气王”到“手气垫底”的数学游戏💰

590 阅读5分钟

一、红包算法的核心要求:既要公平,又要刺激!

设计红包算法时,我们需要满足以下四个核心要求:

  1. 公平性:每个人都有机会抢到红包,金额不能为负(否则会哭晕在厕所😭)。
  2. 随机性:红包金额不能固定,否则大家会失去抢红包的乐趣(想象一下,红包金额永远是1元,你会有多无聊?🙃)。
  3. 不可预测性:顺序不能完全决定金额,否则“手速党”会垄断所有红包(谁愿意当“手气垫底”呢?😎)。
  4. 总金额控制:红包的总金额必须固定,不能多也不能少(否则老板会骂你!😤)。

50bd433b9d2c69bee37f9b6b3e2d419.png

二、红包算法的两大流派:线段切割 vs 二倍均值

1️⃣ 线段切割法:拼手速的“勇士”游戏!

原理
把总金额想象成一根线段,随机切N-1刀,分成N段。每一段的长度就是红包的金额。

优点

  • 算法简单,实现容易。
  • 红包金额的随机性极强,适合“拼手速”的场景(比如春节抢红包)。

缺点

  • 可能出现极端情况,比如有人抢到0.01元,有人抢到99.99元(这种情况会让“手气王”笑出声,其他人默默流泪😢)。

image.png

✅ 线段切割法代码

/**
 * 红包分配算法 - 线段切割法(修正版)
 * @param {number} total - 总金额(单位:元)
 * @param {number} num - 红包数量
 * @returns {number[]} 红包金额数组
 */
function hongbao(total, num) {
    // 初始化结果数组
    const arr = [];
    // 剩余金额(单位:元)
    let restAmount = total;
    // 剩余人数(红包数量)
    let restNum = num;

    // 循环生成 num-1 个红包(最后一个红包由剩余金额自动补足)
    for (let i = 0; i < num - 1; i++) {
        // 计算当前红包的最小金额(0.01元)
        const min = 0.01;
        // 计算当前红包的最大金额(剩余金额/剩余人数 * 2,即“二倍均值法”上限)
        const max = (restAmount / restNum) * 2;
        // 生成随机金额:范围是 [min, max]
        // Math.random() 生成 0~1 的随机小数,乘以 (max - min) 后加上 min 得到目标范围
        const amount = (Math.random() * (max - min) + min).toFixed(2);
        // 更新剩余金额(注意:需要将字符串转为数字)
        restAmount -= parseFloat(amount);
        // 剩余人数减1
        restNum--;
        // 将当前红包金额加入数组
        arr.push(parseFloat(amount));
    }

    // 最后一个红包金额 = 剩余金额(自动补足总金额)
    arr.push(restAmount.toFixed(2));
    return arr;
}

注释重点

  • Math.random():生成0到1之间的随机小数,需要手动计算范围。
  • toFixed(2):保留两位小数(如1.23),但会返回字符串,需用parseFloat()转为数字。
  • restAmountrestNum:动态跟踪剩余金额和剩余人数,确保总金额精确分配。

2️⃣ 二倍均值法:家庭聚会的“和平使者”!

原理
每次分配红包时,随机金额的范围是 [0.01, 剩余金额/剩余人数 * 2]。这样可以保证每个红包的平均值相近,避免“手气王”和“手气垫底”的极端情况。

优点

  • 红包金额分布更均匀,适合家庭聚会等需要“和谐”的场景。
  • 避免出现0元红包(谁也不愿意抢到0元吧?😱)。

缺点

  • 算法复杂度略高,需要动态调整范围。

image.png


✅ 二倍均值法代码

/**
 * 红包分配算法 - 二倍均值法(推荐版)
 * @param {number} total - 总金额(单位:元)
 * @param {number} num - 红包数量
 * @returns {number[]} 红包金额数组
 */
function hongbao(total, num) {
    // 初始化结果数组
    const arr = [];
    // 将总金额转换为“分”(整数),避免浮点数精度问题(如0.1+0.2=0.30000000000000004)
    const totalCents = Math.round(total * 100);
    // 剩余金额(单位:分)
    let restAmount = totalCents;
    // 剩余人数(红包数量)
    let restNum = num;

    // 循环生成 num-1 个红包(最后一个红包由剩余金额自动补足)
    for (let i = 0; i < num - 1; i++) {
        // 计算当前平均值(剩余金额/剩余人数)
        const avg = restAmount / restNum;
        // 计算当前红包的最大金额(二倍均值法上限)
        const max = avg * 2;
        // 生成随机金额:范围是 [1分, max-1分](避免最后一个红包为0)
        // Math.floor() 向下取整,确保金额为整数分
        const amount = Math.floor(Math.random() * (max - 1) + 1);
        // 更新剩余金额(整数分)
        restAmount -= amount;
        // 剩余人数减1
        restNum--;
        // 将当前红包金额转换为元并保留两位小数(如123分→1.23元)
        arr.push((amount / 100).toFixed(2));
    }

    // 最后一个红包金额 = 剩余金额(自动补足总金额)
    arr.push((restAmount / 100).toFixed(2));
    return arr;
}

注释重点

  • 浮点数精度问题:用“分”代替“元”进行计算,最后再转回“元”,避免0.1+0.2=0.30000000000000004的问题。
  • Math.floor():生成整数分,确保最后一个红包不会为0。
  • 二倍均值法:通过动态调整范围,让红包金额更接近平均值,避免极端情况。

三、红包算法的“灵魂拷问”:公平还是刺激?

方法优点缺点适用场景
线段切割法实现简单,随机性强可能出现极端金额春节、双十一等大型活动
二倍均值法分布均匀,避免极端情况实现稍复杂家庭聚会、朋友聚餐

四、红包算法的“终极奥义”:让小白也能看懂的代码!

🎁 示例:调用红包算法

const result = hongbao(100, 5); // 100元分5个红包
console.log(result); // 输出类似:["12.34", "23.45", "15.67", "30.00", "18.60"]

输出解释

  • 总金额100元被分成5个红包,每个金额保留两位小数。
  • 所有金额之和严格等于100元(无误差)。

五、总结:红包算法的“魔法”在于平衡!

红包算法的本质,其实是一个在公平性与随机性之间寻找平衡的数学游戏。无论是线段切割法还是二倍均值法,它们的目标都是让红包既有趣又公平。

下次当你抢到一个大红包时,不妨想一想:这个金额背后,是否藏着一个精心设计的算法?也许,这就是红包的魅力所在吧!🎉


最后提醒
红包虽好,可不要沉迷哦!毕竟“手气王”也是靠运气,不是实力!😄