基本思路:計算平均值,隨機生成小於平均值的金額,扣除後重新計算剩餘金額與平均值,最後一個紅包領走剩餘所有金額。
public class WeChatRedPacket {
public static void distribute(double totalAmount, int count) {
double min = 0.01; // 最小金額 1 分
Random random = new Random();
DecimalFormat df = new DecimalFormat("#.##"); // 保留兩位小數
for (int i = 1; i < count; i++) {
// 隨機範圍:最小值 到 剩餘金額
double amount = Double.parseDouble(df.format(
min + random.nextDouble() * (totalAmount - min)
));
totalAmount = Double.parseDouble(df.format(totalAmount - amount));
System.out.printf("第 %d 個紅包:%.2f 元,餘額:%.2f 元%n", i, amount, totalAmount);
}
System.out.printf("最後一個紅包:%.2f 元%n", totalAmount);
}
public static void main(String[] args) {
distribute(10.26, 9); // 10.26 元,9 個紅包
}
}
public static double[] distributeV2(double totalAmount, int count) {
Random r = new Random();
DecimalFormat df = new DecimalFormat(".##");
double[] result = new double[count];
double remaining = totalAmount;
double avg = Double.parseDouble(df.format(totalAmount / count));
int idx = 0;
for (int i = count; i > 0; i--) {
if (i == 1) {
result[idx] = Double.parseDouble(df.format(remaining));
} else {
double amount;
while (true) {
amount = Double.parseDouble(df.format(r.nextDouble() * remaining));
if (amount > 0 && amount < avg * 2) break; // 限制最大不超過 2 倍平均
}
remaining = Double.parseDouble(df.format(remaining - amount));
result[idx] = amount;
avg = Double.parseDouble(df.format(remaining / (i - 1))); // 重新計算平均
idx++;
}
}
return result;
}
public static void distributeSafe(double total, int count, double min) {
DecimalFormat df = new DecimalFormat("#.##");
for (int i = 1; i < count; i++) {
// 安全上限:確保剩餘 (count-i) 個紅包至少能分到最小值
double safeMax = (total - (count - i) * min) / (count - i);
double amount = min + Math.random() * (safeMax - min);
amount = new BigDecimal(amount).setScale(2, RoundingMode.HALF_UP).doubleValue();
total = new BigDecimal(total - amount).setScale(2, RoundingMode.HALF_UP).doubleValue();
System.out.printf("第 %d 個:%.2f 元,餘額:%.2f 元%n", i, amount, total);
}
System.out.printf("第 %d 個:%.2f 元%n", count, total);
}
public static void distributeAdvanced(int count, float total) {
float min = 1; // 最小 1 元
List<Float> amounts = new ArrayList<>();
DecimalFormat df = new DecimalFormat("###.##");
for (int i = 1; i < count; i++) {
// 最大值:確保剩餘紅包不小於最小值
float max = total - min * (count - i);
int k = (count - i) / 2;
if (count - i <= 2) k = count - i; // 最後兩個特殊處理
max = max / k; // 限制在平均線附近
// 隨機生成 [min, max] 區間金額
float amount = (min * 100 + (float) Math.random() * (max * 100 - min * 100 + 1)) / 100;
amount = Float.parseFloat(df.format(amount));
total -= amount;
amounts.add(amount);
System.out.printf("第 %d 個:%.2f 元,剩餘:%.2f 元%n", i, amount, total);
}
amounts.add(total);
System.out.printf("第 %d 個:%.2f 元%n", count, total);
// 找出手氣最佳
int bestIdx = amounts.indexOf(Collections.max(amounts));
System.out.println("手氣最佳:第 " + (bestIdx + 1) + " 個");
}
上述算法時間複雜度 O(n),完全滿足高併發紅包場景。實際生產環境建議結合 Lua 腳本在 Redis 中原子執行,避免分佈式一致性問題。