398. Random Pick Index
Problem Description
You are given an integer array nums
that may contain duplicate values. Your task is to implement a class that can randomly select and return an index where a given target number appears in the array.
The class should support two operations:
-
Constructor
Solution(int[] nums)
: Initialize the object with the input arraynums
. -
Pick Method
int pick(int target)
: Given a target value, return a random indexi
wherenums[i] == target
. If the target appears multiple times in the array, each valid index should have an equal probability of being selected.
Key requirements:
- The target number is guaranteed to exist in the array
- When multiple indices contain the target value, the selection must be uniformly random (each valid index has equal chance of being picked)
- The array may contain duplicates
For example, if nums = [1, 2, 3, 3, 3]
and you call pick(3)
, the method should randomly return one of the indices 2, 3, or 4, with each having a 1/3 probability of being selected.
The solution uses Reservoir Sampling, a technique for randomly selecting k items from a stream of unknown size. In this case, we're selecting 1 item (k=1) from all indices that match the target. As we iterate through the array:
- We maintain a count
n
of how many times we've seen the target - For each occurrence, we randomly decide with probability
1/n
whether to update our answer to the current index - This ensures each valid index has equal probability of being the final result
Intuition
The challenge here is that we need to randomly select from indices we don't know in advance. We could scan the array first to find all indices where the target appears, store them, and then randomly pick one - but this would require extra space proportional to the number of occurrences.
Instead, we can use a clever approach that processes the array in a single pass while maintaining equal probability for each valid index. Think of it this way: imagine you're going through the array and you encounter the target values one by one.
- When you see the first occurrence, you have no choice - it becomes your current answer with probability
1/1 = 100%
. - When you see the second occurrence, you want both indices to have equal chance. So you keep the new index with probability
1/2
and keep the old one with probability1/2
. - When you see the third occurrence, you want all three to have equal chance (
1/3
each). If you pick the new one with probability1/3
, and keep the old answer with probability2/3
, then each of the three indices ends up with1/3
probability overall.
This pattern continues: for the n-th
occurrence, we select it with probability 1/n
and keep the previous answer with probability (n-1)/n
.
Mathematically, this works because:
- The first element has probability:
1 × 1/2 × 2/3 × 3/4 × ... × (n-1)/n = 1/n
- The second element has probability:
1/2 × 2/3 × 3/4 × ... × (n-1)/n = 1/n
- The k-th element has probability:
1/k × k/(k+1) × ... × (n-1)/n = 1/n
Each valid index ends up with exactly 1/n
probability of being selected, achieving uniform randomness without needing to store all indices.
Solution Approach
The implementation uses the Reservoir Sampling algorithm (specifically for k=1) to achieve uniform random selection in a single pass through the array.
Data Structure:
- We only store the original array
nums
in the constructor - No additional data structures are needed to track indices
Implementation Details:
-
Constructor: Simply stores the input array for later use.
def __init__(self, nums: List[int]): self.nums = nums
-
Pick Method: Implements the reservoir sampling algorithm:
def pick(self, target: int) -> int: n = ans = 0 for i, v in enumerate(self.nums): if v == target: n += 1 x = random.randint(1, n) if x == n: ans = i return ans
Algorithm Walkthrough:
-
Initialize two variables:
n
: Counter for how many times we've seen the targetans
: Stores the current selected index
-
Iterate through the array with index
i
and valuev
:- When we find a value equal to target:
- Increment the counter
n
- Generate a random number
x
between 1 andn
(inclusive) - If
x == n
, updateans
to the current indexi
- This gives the current index a
1/n
probability of being selected
- Increment the counter
- When we find a value equal to target:
-
Return the final value of
ans
Why this works:
- The condition
if x == n
has probability1/n
of being true - This means each new occurrence has exactly
1/n
chance of replacing the previous answer - Through mathematical induction, this ensures each valid index has equal probability
1/total_occurrences
of being the final answer
Time and Space Complexity:
- Time:
O(n)
for each pick operation, where n is the length of the array - Space:
O(1)
additional space (not counting the input array storage)
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with nums = [1, 2, 3, 3, 3]
and calling pick(3)
.
Initial State:
n = 0
(count of target occurrences found)ans = 0
(current selected index)
Iteration Process:
Index 0, value = 1:
1 ≠ 3
, skip
Index 1, value = 2:
2 ≠ 3
, skip
Index 2, value = 3:
- Found target!
n = 1
- Generate random number between 1 and 1:
x = 1
- Since
x == n
(both are 1), updateans = 2
- Current probability: Index 2 selected with 100% chance
Index 3, value = 3:
- Found target!
n = 2
- Generate random number between 1 and 2: let's say
x = 2
- Since
x == n
(both are 2), updateans = 3
- Current probability distribution:
- Index 2: had 100% × (1/2 chance of not being replaced) = 50%
- Index 3: 50% chance of being selected
Index 4, value = 3:
- Found target!
n = 3
- Generate random number between 1 and 3: let's say
x = 1
- Since
x ≠ n
(1 ≠ 3), keepans = 3
- Final probability distribution:
- Index 2: 50% × (2/3 chance of surviving) = 1/3
- Index 3: 50% × (2/3 chance of surviving) = 1/3
- Index 4: 1/3 chance of being selected
Result: Returns ans = 3
in this run
Different Random Outcomes: If we ran this multiple times with different random values:
- Run 1:
x=1
at n=1,x=1
at n=2,x=3
at n=3 → Returns index 4 - Run 2:
x=1
at n=1,x=2
at n=2,x=2
at n=3 → Returns index 3 - Run 3:
x=1
at n=1,x=1
at n=2,x=1
at n=3 → Returns index 2
Over many runs, each of the three indices (2, 3, 4) would be returned approximately 33.33% of the time, achieving uniform randomness.
Solution Implementation
1import random
2from typing import List
3
4class Solution:
5 def __init__(self, nums: List[int]):
6 """
7 Initialize the Solution with an array of integers.
8
9 Args:
10 nums: List of integers that may contain duplicates
11 """
12 self.nums = nums
13
14 def pick(self, target: int) -> int:
15 """
16 Pick a random index where nums[index] == target.
17 Uses reservoir sampling to ensure uniform probability distribution.
18
19 Args:
20 target: The target value to search for in the array
21
22 Returns:
23 A random index i such that nums[i] == target
24 """
25 count = 0 # Counter for number of target values found
26 result_index = 0 # Store the selected index
27
28 # Iterate through the array with index and value
29 for index, value in enumerate(self.nums):
30 if value == target:
31 count += 1
32 # Reservoir sampling: with probability 1/count, update the result
33 # Generate random number from 1 to count (inclusive)
34 random_num = random.randint(1, count)
35 # If random number equals count, update the result index
36 # This ensures each valid index has equal probability of being selected
37 if random_num == count:
38 result_index = index
39
40 return result_index
41
42
43# Your Solution object will be instantiated and called as such:
44# obj = Solution(nums)
45# param_1 = obj.pick(target)
46
1class Solution {
2 private int[] nums;
3 private Random random = new Random();
4
5 /**
6 * Constructor to initialize the Solution with the given array
7 * @param nums The input array
8 */
9 public Solution(int[] nums) {
10 this.nums = nums;
11 }
12
13 /**
14 * Randomly picks an index where nums[index] == target
15 * Uses reservoir sampling algorithm to ensure uniform distribution
16 * @param target The target value to search for
17 * @return A random index where nums[index] == target
18 */
19 public int pick(int target) {
20 int count = 0; // Count of elements equal to target found so far
21 int resultIndex = 0; // The selected index to return
22
23 // Iterate through the entire array
24 for (int i = 0; i < nums.length; i++) {
25 // Check if current element equals target
26 if (nums[i] == target) {
27 count++;
28
29 // Generate random number from 1 to count (inclusive)
30 int randomNum = 1 + random.nextInt(count);
31
32 // With probability 1/count, update the result to current index
33 // This ensures each valid index has equal probability of being selected
34 if (randomNum == count) {
35 resultIndex = i;
36 }
37 }
38 }
39
40 return resultIndex;
41 }
42}
43
44/**
45 * Your Solution object will be instantiated and called as such:
46 * Solution obj = new Solution(nums);
47 * int param_1 = obj.pick(target);
48 */
49
1class Solution {
2public:
3 vector<int> nums;
4
5 /**
6 * Constructor: Initialize the solution with the given array
7 * @param nums - Input array containing integers
8 */
9 Solution(vector<int>& nums) {
10 this->nums = nums;
11 }
12
13 /**
14 * Randomly pick an index of the target value in the array
15 * Uses reservoir sampling algorithm to ensure equal probability
16 * @param target - The target value to search for
17 * @return - A random index where nums[index] == target
18 */
19 int pick(int target) {
20 int count = 0; // Counter for occurrences of target
21 int result = 0; // Store the selected index
22
23 // Iterate through the entire array
24 for (int i = 0; i < nums.size(); ++i) {
25 if (nums[i] == target) {
26 ++count;
27
28 // Reservoir sampling: with probability 1/count, replace the result
29 // Generate random number between 1 and count (inclusive)
30 int randomNum = 1 + rand() % count;
31
32 // If random number equals count, update result to current index
33 if (randomNum == count) {
34 result = i;
35 }
36 }
37 }
38
39 return result;
40 }
41};
42
43/**
44 * Your Solution object will be instantiated and called as such:
45 * Solution* obj = new Solution(nums);
46 * int param_1 = obj->pick(target);
47 */
48
1let nums: number[] = [];
2
3/**
4 * Initialize the solution with the given array
5 * @param _nums - Input array containing integers
6 */
7function Solution(_nums: number[]): void {
8 nums = _nums;
9}
10
11/**
12 * Randomly pick an index of the target value in the array
13 * Uses reservoir sampling algorithm to ensure equal probability
14 * @param target - The target value to search for
15 * @return - A random index where nums[index] == target
16 */
17function pick(target: number): number {
18 let count = 0; // Counter for occurrences of target
19 let result = 0; // Store the selected index
20
21 // Iterate through the entire array
22 for (let i = 0; i < nums.length; i++) {
23 if (nums[i] === target) {
24 count++;
25
26 // Reservoir sampling: with probability 1/count, replace the result
27 // Generate random number between 1 and count (inclusive)
28 const randomNum = Math.floor(Math.random() * count) + 1;
29
30 // If random number equals count, update result to current index
31 if (randomNum === count) {
32 result = i;
33 }
34 }
35 }
36
37 return result;
38}
39
40/**
41 * Your Solution object will be instantiated and called as such:
42 * Solution(nums);
43 * let param_1 = pick(target);
44 */
45
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the input array nums
.
- The
__init__
method takesO(1)
time as it only stores a reference to the input array. - The
pick
method iterates through the entire array once usingenumerate()
, performing constant-time operations for each element (comparison, increment, random number generation, and conditional assignment). Therefore, the time complexity isO(n)
.
Space Complexity: O(1)
for the pick
method itself, O(n)
for the overall solution.
- The
__init__
method stores the input array, which requiresO(n)
space. - The
pick
method uses only a constant amount of extra space with variablesn
,ans
,i
,v
, andx
, regardless of the input size. - Note: This implementation uses reservoir sampling algorithm, which allows selecting a random index with equal probability without storing all target indices separately.
Common Pitfalls
1. Using 0-based Random Range Instead of 1-based
A common mistake is using random.randint(0, count-1)
and checking if random_num == 0
instead of the 1-based approach.
Incorrect Implementation:
def pick(self, target: int) -> int:
count = 0
result_index = 0
for index, value in enumerate(self.nums):
if value == target:
count += 1
random_num = random.randint(0, count-1) # Wrong range!
if random_num == 0: # This doesn't give 1/count probability
result_index = index
return result_index
Why it's wrong: With randint(0, count-1)
when count=1, the range is just [0], so the probability is 100%. When count=2, the range is [0,1], giving 50% probability. This doesn't match the required 1/n probability pattern.
Correct Approach: Use random.randint(1, count)
and check if random_num == count
to ensure exactly 1/n probability.
2. Pre-computing All Indices in Constructor
Some might try to optimize by storing all indices for each value upfront:
Memory-Inefficient Approach:
def __init__(self, nums: List[int]):
self.indices_map = {}
for i, num in enumerate(nums):
if num not in self.indices_map:
self.indices_map[num] = []
self.indices_map[num].append(i)
def pick(self, target: int) -> int:
indices = self.indices_map[target]
return random.choice(indices)
Problems:
- Uses O(n) additional space in worst case
- Defeats the purpose of reservoir sampling's space efficiency
- May cause memory issues with large arrays
3. Forgetting to Initialize Result Variable
Not initializing the result variable or using -1 as default can cause issues:
Problematic Code:
def pick(self, target: int) -> int:
count = 0
# result_index not initialized or initialized to -1
for index, value in enumerate(self.nums):
if value == target:
count += 1
if random.randint(1, count) == count:
result_index = index # NameError if not initialized!
return result_index # May return -1 or undefined
Solution: Always initialize result_index = 0
or to the first valid index found.
4. Using Wrong Probability Check
Some implementations incorrectly check if the random number equals 1 instead of count:
Wrong Probability Logic:
def pick(self, target: int) -> int:
count = 0
result_index = 0
for index, value in enumerate(self.nums):
if value == target:
count += 1
if random.randint(1, count) == 1: # Wrong! This gives different probabilities
result_index = index
return result_index
Why it's wrong: This gives the first occurrence probability 1, second occurrence probability 1/2, third occurrence probability 1/3, etc. The final probabilities won't be uniform.
Correct Logic: Check if random.randint(1, count) == count
to maintain the reservoir sampling property.
In a binary min heap, the maximum element can be found in:
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!