農曆新年期間,搶紅包已成為全民參與的社交活動。作為開發者,我們往往對紅包背後的隨機分配演算法更感興趣。本文將深入剖析一種模擬微信紅包規則的 Java 實作方案,涵演算法設計思路、關鍵程式碼實現以及完整的可執行範例。
微信紅包規定單個紅包最小 1 分(0.01 元),最大 200 元。程式中統一以「分」為單位進行整數運算,避免浮點數精度問題:
// 最小紅包金額(分) private static final int MIN_MONEY = 1; // 最大紅包金額(分),對應 200 元 private static final int MAX_MONEY = 200 * 100;
演算法核心在於確保每一步分配後,剩餘金額仍能合法分配給剩餘紅包。驗證邏輯檢查平均值是否落在區間內:
private boolean isRight(int money, int count) {
double avg = money / (double) count;
return avg >= MIN_MONEY && avg <= MAX_MONEY;
}
採用「生成→驗證→回溯調整」策略:先生成一個隨機金額,若導致剩餘金額非法,則根據平均值偏大或偏小,動態調整下一次隨機範圍並遞迴重試:
private int random(int money, int minS, int maxS, int count) {
if (count == 1) return money; // 最後一個紅包直接返回餘額
if (minS == maxS) return minS; // 範圍收斂直接返回
int max = Math.min(maxS, money);
// 隨機生成 [minS, max] 區間內的整數
int one = (int) (Math.random() * (max - minS + 1)) + minS;
int remaining = money - one;
if (isRight(remaining, count - 1)) {
return one; // 分配合法,確認該金額
} else {
double avg = remaining / (double) (count - 1);
if (avg < MIN_MONEY) {
// 平均值過小,說明當前紅包太大,縮小上限重試
return random(money, minS, one, count);
} else if (avg > MAX_MONEY) {
// 平均值過大,說明當前紅包太小,提高下限重試
return random(money, one, maxS, count);
}
}
return one; // 理論上不會執行到此
}
為防止單個紅包佔用過多資金,引入倍數係數 TIMES 限制非最後紅包的最大值(預設為平均值的 2.1 倍),並迭代調用上述隨機函數:
private static final double TIMES = 2.1;
public List<Integer> splitRedPackets(int totalMoney, int count) {
if (!isRight(totalMoney, count)) return null;
List<Integer> packets = new ArrayList<>();
int remainingMoney = totalMoney;
int remainingCount = count;
for (int i = 0; i < count; i++) {
// 動態計算當前紅包上限
int max = (int) (remainingMoney * TIMES / remainingCount);
max = Math.min(max, MAX_MONEY);
int packet = random(remainingMoney, MIN_MONEY, max, remainingCount);
packets.add(packet);
remainingMoney -= packet;
remainingCount--;
}
return packets;
}
以 200 元(20000 分)、100 個紅包為例測試,分配結果符合預期分佈:

package com.lulei.weixin.util;
import java.util.ArrayList;
import java.util.List;
public class RedPacketUtil {
private static final int MIN_MONEY = 1;
private static final int MAX_MONEY = 200 * 100;
private static final double TIMES = 2.1;
public List<Integer> splitRedPackets(int money, int count) {
if (!isRight(money, count)) return null;
List<Integer> list = new ArrayList<>();
int max = (int) (money * TIMES / count);
max = max > MAX_MONEY ? MAX_MONEY : max;
for (int i = 0; i < count; i++) {
int one = random(money, MIN_MONEY, max, count - i);
list.add(one);
money -= one;
}
return list;
}
private int random(int money, int minS, int maxS, int count) {
if (count == 1) return money;
if (minS == maxS) return minS;
int max = maxS > money ? money : maxS;
int one = ((int) Math.rint(Math.random() * (max - minS) + minS)) % max + 1;
int money1 = money - one;
if (isRight(money1, count - 1)) {
return one;
} else {
double avg = money1 / (double) (count - 1);
if (avg < MIN_MONEY) {
return random(money, minS, one, count);
} else if (avg > MAX_MONEY) {
return random(money, one, maxS, count);
}
}
return one;
}
private boolean isRight(int money, int count) {
double avg = money / (double) count;
return avg >= MIN_MONEY && avg <= MAX_MONEY;
}
// 測試主方法
public static void main(String[] args) {
RedPacketUtil util = new RedPacketUtil();
System.out.println(util.splitRedPackets(20000, 100));
}
}
該演算法通過「邊界約束 + 遞迴回溯」的方式,優雅地解決了隨機紅包分配中「既要隨機、又要合法、還要公平」的工程難題。實際應用時建議增加併發鎖、分佈式 ID 生成等企業級增強功能。