LeetCode 528. 按权重随机选择
题目描述


题意分析
给定一组固定的正整数权重
w,每次调用pickIndex()随机返回一个下标i,要求它被选中的概率恰好为w[i] / sum(w)。权重表示抽中机会所占的比例,不是返回值;题目要求返回下标。各次调用都按同一分布抽样,不需要扣减权重,也不要求少量调用后的实际次数严格符合比例。
解法:前缀和 + 二分定位随机数
核心思路
[!blue]
如果把下标
i重复放入数组w[i]次,再均匀抽取一个位置,就能得到所需概率,但这样会占用与总权重相关的空间。前缀和可以只记录每段重复区域的边界,不必真的展开这些位置。令
prefix[i]为从w[0]到w[i]的累加和,总和为total。下标0对应[0, prefix[0]),其余下标i对应[prefix[i - 1], prefix[i])。这些区间互不重叠,完整覆盖[0, total),每个区间恰好含有w[i]个整数。每次在
[0, total)中等概率抽取整数target,再返回它所在区间的下标。所有整数位置都等概率,落入第i段的概率就等于该段整数个数除以总个数,即w[i] / total。因为所有权重为正,前缀和严格递增,区间定位就是查找第一个严格大于
target的前缀和。若prefix[mid] > target,答案可能就是mid或在它左边,保留mid;否则当前段已经结束,答案必在右边。最终左右边界相遇的位置就是目标下标。随机值一定小于最后一个前缀和
total,因此答案必然存在。构造时一次完成前缀和,之后每次查询只需一次随机抽样和一次二分。题目约束下总权重不超过10^9,代码使用的整数类型可以保存。
解题步骤
- 构造时从左到右累加权重,保存
prefix和总权重total。- 查询时均匀生成满足
0 <= target < total的整数。- 在前缀和数组中二分:若
prefix[mid] > target,令right = mid;否则令left = mid + 1。- 当
left == right时返回该下标,它对应唯一包含target的区间。
代码实现
class Solution {
private final int[] prefix;
private final int total;
public Solution(int[] w) {
prefix = new int[w.length];
int sum = 0;
for (int i = 0; i < w.length; i++) {
sum += w[i];
prefix[i] = sum;
}
total = sum;
}
public int pickIndex() {
int target = java.util.concurrent.ThreadLocalRandom.current().nextInt(total);
int left = 0;
int right = prefix.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
// 随机位置取自零到总权重减一,因此查找严格大于它的首个前缀。
if (prefix[mid] > target) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
}
import "math/rand"
type Solution struct {
prefix []int
total int
}
func Constructor(w []int) Solution {
prefix := make([]int, len(w))
sum := 0
for i, weight := range w {
sum += weight
prefix[i] = sum
}
return Solution{prefix: prefix, total: sum}
}
func (this *Solution) PickIndex() int {
target := rand.Intn(this.total)
left, right := 0, len(this.prefix)-1
for left < right {
mid := left + (right-left)/2
// 随机位置取自零到总权重减一,因此查找严格大于它的首个前缀。
if this.prefix[mid] > target {
right = mid
} else {
left = mid + 1
}
}
return left
}
复杂度分析
设权重数组长度为 $n$。
- 时间复杂度:构造为 $O(n)$,单次查询为 $O(\log n)$,仅一个权重时查询为常数时间。
- 空间复杂度:$O(n)$,保存前缀和;单次查询只使用常数个变量。
关键点总结
[!green]
- 前缀和隐式表示长度与权重成正比的整数区间,均匀抽位置即按权重抽下标。
[0, total)的随机范围必须与“第一个严格大于目标”的二分条件配套。- 正权重保证前缀和严格递增,最后一个前缀和保证二分答案存在。
易错点总结
[!yellow]
- 使用
[0, total)抽样却查询>= target,会把落在段边界上的位置错误归给前一段。- 命中
prefix[mid] > target时不能排除mid,它本身可能就是答案,应使用right = mid。- 另一分支应使用
left = mid + 1,写成left = mid可能无法缩小区间。- 不要按权重真的展开数组,空间会随
total增长,失去前缀和表示的优势。- 返回的是区间下标,不是前缀和值;也不能用固定轮流返回的方式代替随机抽样。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 497. 非重叠矩形中的随机点 | 中等 | 都把均匀随机整数放入累计权重区间并二分定位;该题每个矩形的格点数就是权重,选中矩形后再定位其中的格点。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!