1.8 KiB
1.8 KiB
权重抽奖
轮盘赌
适合奖品数量较少,一次性抽奖的场景
核心顺序遍历,逐个减权重:是用一个随机数 r,依次减去每个奖品的权重,当 r 变成负数的那一刻,说明它“落入了”当前奖品的权重区间。时间复杂度 O(n),空间复杂度O(1)
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元券→←月卡→←年卡→←大奖→
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]
}