import random
import bisect
class Solution:
def __init__(self, w):
self.prefix_sums = []
prefix = 0
for weight in w:
prefix += weight
self.prefix_sums.append(prefix)
self.total = prefix
def pickIndex(self):
target = random.randint(1, self.total)
# Find the index where target would fit in prefix_sums
return bisect.bisect_left(self.prefix_sums, target)
#include <vector>
#include <cstdlib>
#include <numeric>
#include <algorithm>
using namespace std;
class Solution {
vector<int> prefixSums;
int total;
public:
Solution(vector<int>& w) {
prefixSums.resize(w.size());
partial_sum(w.begin(), w.end(), prefixSums.begin());
total = prefixSums.back();
}
int pickIndex() {
int target = rand() % total + 1;
return lower_bound(prefixSums.begin(), prefixSums.end(), target) - prefixSums.begin();
}
};
import java.util.*;
class Solution {
private int[] prefixSums;
private int total;
public Solution(int[] w) {
prefixSums = new int[w.length];
int prefix = 0;
for (int i = 0; i < w.length; i++) {
prefix += w[i];
prefixSums[i] = prefix;
}
total = prefix;
}
public int pickIndex() {
int target = (int)(Math.random() * total) + 1;
int left = 0, right = prefixSums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (prefixSums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}
class Solution {
constructor(w) {
this.prefixSums = [];
let prefix = 0;
for (let i = 0; i < w.length; i++) {
prefix += w[i];
this.prefixSums.push(prefix);
}
this.total = prefix;
}
pickIndex() {
let target = Math.floor(Math.random() * this.total) + 1;
// Binary search for the first prefixSum >= target
let left = 0, right = this.prefixSums.length - 1;
while (left < right) {
let mid = Math.floor((left + right) / 2);
if (this.prefixSums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}
The "Random Pick with Weight" problem asks you to implement a data structure that, given a list of positive integers w
, allows you to randomly pick an index such that the probability of picking index i
is proportional to w[i]
. In other words, the higher the weight at an index, the more likely it is to be chosen.
You must implement two methods:
__init__(self, w)
: Initializes the object with the given list of weights w
.pickIndex(self)
: Randomly returns an index according to the weight distribution.Key constraints:
pickIndex
must be independent and follow the probability distribution defined by the weights.pickIndex
after initialization.
At first glance, it might seem natural to simulate the process by creating a large array in which each index appears as many times as its weight, and then randomly picking from this array. For example, if w = [1, 3, 2]
, you could make an array [0, 1, 1, 1, 2, 2]
and pick a random element.
However, this approach is inefficient, especially when weights are large, as it uses a lot of extra space and is slow for large inputs. The key is to find a way to map a random number to an index based on the weights, without explicitly building the expanded array.
The optimized idea is to use prefix sums and binary search. By treating the weights as segments on a number line, we can pick a random number in the total weight range and find which segment it falls into. This approach is both space and time efficient.
The efficient solution uses prefix sums and binary search to map random numbers to weighted indices. Here’s a step-by-step breakdown:
i
in this array represents the sum of all weights up to and including w[i]
.w = [2, 5, 3]
, the prefix sums are [2, 7, 10]
.1
and the total sum of weights (inclusive). This simulates picking a random point on the number line formed by the weights.This method is efficient because:
pickIndex
call takes O(log n) time due to binary search.
Let's walk through an example with w = [1, 3, 2]
:
[1, 4, 6]
Brute-force approach:
pickIndex
: O(log n) time for binary search, O(1) space.This is a significant improvement, especially for large weights or many calls.
The "Random Pick with Weight" problem is elegantly solved using prefix sums and binary search. By mapping each weight to a segment on a number line and picking a random point, we can efficiently select indices according to their weights. The approach is both time and space efficient, making it suitable for repeated random picks and large input sizes.
Key insights include: