## 权重抽奖 ### 轮盘赌 适合奖品数量较少,一次性抽奖的场景 核心顺序遍历,逐个减权重:是用一个随机数 `r`,依次减去每个奖品的权重,当 `r` 变成负数的那一刻,说明它“落入了”当前奖品的权重区间。时间复杂度 **O(n)**,空间复杂度**O(1)** ```go func Draw(rewards []*Reward) *Reward { totalWeight := 0 for _, r := range rewards { totalWeight += r.Weight } r := rand.Intn(totalWeight) // [0, totalWeight) for _, reward := range rewards { r -= reward.Weight if r < 0 { return reward } } return rewards[len(rewards)-1] // 兜底,理论上不会走到 } ``` ### 前缀和 + 二分查找 适合奖品数量较多,需要频繁抽奖的通用场景 核心是构建前缀和数组,二分查找:把权重想象成一条线段,每个奖品占据一段,生成 `[0, 100)` 的随机数,落在哪个区间就中哪个奖品。二分查找快速定位区间,时间复杂度 **O(log n)**,空间复杂度**O(n)** ``` 权重: 50 25 15 7 2 1 ├─────────┼────────┼──────┼────┼──┼──┤ 0 50 75 90 97 99 100 ← 谢谢参与 →←5元券→←10元券→←月卡→←年卡→←大奖→ ``` ```go type Reward struct { Name string Weight int } func Draw(rewards []*Reward) *Reward { prefixSumArray := make([]int, len(rewards)) // 前缀和数组 totalWeight := 0 // 总权重 for i, p := range rewards { totalWeight += p.Weight prefixSumArray[i] = totalWeight fmt.Println(totalWeight) } r := rand.Intn(totalWeight) // r ∈ [0, totalWeight-1] idx := sort.SearchInts(prefixSumArray, r+1) // 查找目标 ∈ [1, totalWeight] return rewards[idx] } ```