import random
class Solution:
def __init__(self, nums):
self.nums = nums
def pick(self, target):
count = 0
res = -1
for i, num in enumerate(self.nums):
if num == target:
count += 1
if random.randint(1, count) == 1:
res = i
return res
#include <vector>
#include <cstdlib>
class Solution {
public:
std::vector<int> nums;
Solution(std::vector<int>& nums) {
this->nums = nums;
}
int pick(int target) {
int count = 0, res = -1;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] == target) {
++count;
if (rand() % count == 0) {
res = i;
}
}
}
return res;
}
};
import java.util.*;
class Solution {
private int[] nums;
private Random rand;
public Solution(int[] nums) {
this.nums = nums;
this.rand = new Random();
}
public int pick(int target) {
int count = 0, res = -1;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target) {
count++;
if (rand.nextInt(count) == 0) {
res = i;
}
}
}
return res;
}
}
var Solution = function(nums) {
this.nums = nums;
};
Solution.prototype.pick = function(target) {
let count = 0, res = -1;
for (let i = 0; i < this.nums.length; i++) {
if (this.nums[i] === target) {
count++;
if (Math.floor(Math.random() * count) === 0) {
res = i;
}
}
}
return res;
};
You are given an integer array nums
that may contain duplicates. Implement a class with a method pick(target)
that returns a random index where the value of nums
at that index is equal to target
. Each valid index should have an equal probability of being returned.
Key constraints:
target
.pick(target)
must return one of these indices, chosen uniformly at random.pick
method may be called many times.nums
for preprocessing.
The first idea that might come to mind is to store all indices of target
in a separate list, and then randomly pick one. But this requires extra space proportional to the number of occurrences of target
, and if the array is very large or the method is called frequently, this can be inefficient.
Instead, we need a way to pick a random index "on the fly" without creating a list of all valid indices. This is a classic case for an algorithm called Reservoir Sampling, which allows us to select one random item from a stream (or array) where the number of qualifying items is not known in advance.
The challenge is to ensure that every index with value target
has an equal chance of being selected, even as we scan through the array only once.
We use the Reservoir Sampling algorithm for this problem. Here’s how it works step-by-step:
target
so far (count
), and another to store the current chosen index (res
).
i
:
nums[i]
is not equal to target
, skip it.nums[i]
equals target
, increment count
.1
and count
(inclusive). If the random number is 1
(or 0
depending on language), update res
to i
.
res
holds the index of a randomly selected occurrence of target
.
This approach ensures that each valid index is chosen with equal probability, and it only requires constant extra space regardless of the input size.
Why Reservoir Sampling? Because it allows us to pick a random item from a stream (or array) where the total number of qualifying items is not known beforehand, and it does so using only constant space.
Suppose nums = [1, 2, 3, 3, 3]
and we call pick(3)
.
nums[0]=1
(not 3), skip.nums[1]=2
(not 3), skip.nums[2]=3
(first occurrence of 3): count=1
. Random number between 1 and 1 is always 1, so res=2
.nums[3]=3
(second occurrence): count=2
. Random number between 1 and 2. If 1, res=3
; else, keep previous.nums[4]=3
(third occurrence): count=3
. Random number between 1 and 3. If 1, res=4
; else, keep previous.
At the end, res
will be one of 2, 3, or 4, each with probability 1/3. If you run pick(3)
many times, you'll see each index is chosen about equally often.
pick(target)
call, collect all indices where nums[i] == target
into a list (O(n) time and O(n) space), then pick one at random (O(1) time).
pick(target)
call, scan the array once (O(n) time), but use only O(1) extra space.
Since the problem restricts extra space, the reservoir sampling solution is optimal: O(n) time per pick, O(1) space.
The Random Pick Index problem is elegantly solved using reservoir sampling, allowing us to select a random index of a target value with equal probability and without using extra space proportional to the input size. The key insight is that, by updating the chosen index with decreasing probability as we see more occurrences, we ensure fairness and efficiency. This approach is both simple and powerful, and is widely applicable in streaming and sampling scenarios.