lab/note/资源/算法/抽奖算法.md
张育新 be711334fb docs(note): 添加笔记目录及 gitignore 规则
- 新增 note 目录包含 AI、SQL、Golang、网络等多领域学习笔记
- 更新 .gitignore 添加 note/assets/ 和 note/项目/ 忽略规则
2026-07-10 09:24:40 +08:00

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]
}