61 lines
1.8 KiB
Markdown
61 lines
1.8 KiB
Markdown
## 权重抽奖
|
|
|
|
### 轮盘赌
|
|
|
|
适合奖品数量较少,一次性抽奖的场景
|
|
|
|
核心顺序遍历,逐个减权重:是用一个随机数 `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]
|
|
}
|
|
```
|
|
|