Random Pick with Weight
Problem
w, one per index. Implement a class
Solution(int[] w) whose pickIndex() returns a random index
i with probability w[i] / sum(w). Many picks are made after a
single construction, so pickIndex should be fast.w = [1]; pickIndex()Output0 (the only index)w = [1,3]; many pickIndex()Output0 about 25% of the time, 1 about 75%1 ≤ w.length ≤ 104 · 1 ≤ w[i] ≤ 105 · ≤ 104 calls to pickIndex
Try it yourself
class Solution {
public Solution(int[] w) {
}
public int pickIndex() {
}
}
total:
index i owns a segment of length w[i]. Draw a uniform ticket in
[0, total); the segment it lands in is the answer, and the prefix sums
are the sorted segment ends, so binary search finds it.Instinct Sorted data, a rotated array, or find the min/max X that works where a yes/no test flips only once - halve the search space every step.
Memory hack Find the fence - every candidate reads NO NO NO YES YES, and binary search just walks to where the fence stands.
Open the Binary Search guide →Approaches
Brute ForceExpanded Ticket Pool
O(total) build, O(1) pickSpace O(total)Literally hand out tickets: put index i into a pool
w[i] times and pick a uniformly random slot. Each index is chosen in
proportion to how many tickets it holds.
class Solution { Random rand = new Random(); private final int[] pool; // index i appears w[i] times public Solution(int[] w) { int total = 0; for (int x : w) total += x; pool = new int[total]; int k = 0; for (int i = 0; i < w.length; i++) { for (int c = 0; c < w[i]; c++) pool[k++] = i; } } public int pickIndex() { return pool[rand.nextInt(pool.length)]; } }
Instant picks, but total can reach 109 — the pool will not fit in memory.
BetterPrefix Sums + Linear Scan
O(n) build, O(n) pickSpace O(n)Instead of storing every ticket, store where each index’s run of tickets
ends: prefix[i] = w[0] + … + w[i]. A ticket r in
[0, total) belongs to the first index with r < prefix[i],
found by scanning.
class Solution { Random rand = new Random(); private final int[] prefix; // prefix[i] = w[0] + ... + w[i] 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; } } public int pickIndex() { int r = rand.nextInt(prefix[prefix.length - 1]); // ticket in [0, total) for (int i = 0; i < prefix.length; i++) { if (r < prefix[i]) return i; // ticket falls in index i's segment } return prefix.length - 1; // unreachable } }
Memory drops to O(n), but each pick may scan all 104 entries.
OptimalPrefix Sums + Binary Search
O(n) build, O(log n) pickSpace O(n)The prefix array is strictly increasing (weights are positive), so “first
index with prefix[i] > r” is an upper-bound search. Each index
i owns exactly w[i] ticket values, so the distribution is
exact.
class Solution { Random rand = new Random(); private final int[] prefix; // prefix[i] = w[0] + ... + w[i] 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; } } public int pickIndex() { int r = rand.nextInt(prefix[prefix.length - 1]); // ticket in [0, total) int lo = 0, hi = prefix.length - 1; while (lo < hi) { // first i with prefix[i] > r int mid = lo + (hi - lo) / 2; if (prefix[mid] > r) hi = mid; else lo = mid + 1; } return lo; } }
Dry runw = [1]; pickIndex()
- w=[1,3,2]146prefix sums, total=6
- r=3, lo=0 hi=21lo4m6hi4 > 3 → hi=1
- lo=0 hi=11lo m4hi61 ≤ 3 → lo=1
- lo=hi=114lo hi6pick index 1 ✔
Same tiny memory as the scan with logarithmic picks — the standard answer.
1..3 map to index 1 here: exactly w[1] = 3
values, which is why the probabilities come out right.At a glance
| Level | Approach | Time | Space |
|---|---|---|---|
| Brute Force | Expanded Ticket Pool | O(total) build, O(1) pick | O(total) |
| Better | Prefix Sums + Linear Scan | O(n) build, O(n) pick | O(n) |
| Optimal | Prefix Sums + Binary Search | O(n) build, O(log n) pick | O(n) |
- Mixing ranges: ticket in
[0, total)needsprefix[i] > r, not ≥. - Drawing a
doubleand comparing loosely — off-by-one bias at boundaries. - Recomputing prefix sums inside
pickIndexinstead of once in the constructor.
- Draw the number line with segments of length
w[i]before writing code. - Check the boundary: the largest ticket
total - 1must map to the last index. - Mention the alias method for O(1) picks if asked about heavy query loads.