## 权重抽奖

### 轮盘赌

适合奖品数量较少，一次性抽奖的场景

核心顺序遍历，逐个减权重：是用一个随机数 `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]
}
```

