380. Insert Delete GetRandom O(1)
Problem Description
You need to design a data structure called RandomizedSet
that supports inserting values, removing values, and getting a random element from the set - all in average O(1) time complexity.
The RandomizedSet
class should support the following operations:
-
RandomizedSet()
: Creates and initializes an empty RandomizedSet object. -
insert(val)
: Adds the integerval
to the set if it's not already present. Returnstrue
if the value was successfully added (wasn't in the set before), andfalse
if the value was already in the set. -
remove(val)
: Removes the integerval
from the set if it exists. Returnstrue
if the value was successfully removed (was in the set), andfalse
if the value wasn't in the set. -
getRandom()
: Returns a random element from the current set. Each element in the set must have equal probability of being selected. This method will only be called when there is at least one element in the set.
The key challenge is implementing all three operations with average O(1) time complexity. The solution uses a combination of a hash table (dictionary) and a dynamic array (list) to achieve this:
- The hash table
d
maps each value to its index in the array - The array
q
stores all the values in the set
For insertion, we append the new value to the end of the array and record its index in the hash table. For removal, we swap the element to be removed with the last element in the array, update the hash table accordingly, then pop the last element. This avoids the O(n) cost of removing from the middle of an array. For getting a random element, we simply pick a random index from the array.
Intuition
To achieve O(1) average time complexity for all operations, we need to think about the strengths and weaknesses of different data structures.
For insert
and remove
operations, a hash table immediately comes to mind since it provides O(1) average time for checking existence, adding, and deleting elements. However, hash tables don't support getting a random element efficiently - we'd need to iterate through all keys which takes O(n) time.
For getRandom
, an array is perfect because we can generate a random index and access that element in O(1) time. But arrays have a problem: removing an element from the middle takes O(n) time due to shifting elements.
The key insight is to combine both data structures and use a clever trick for removal:
-
Use an array
q
to store all values - this makesgetRandom
trivial with O(1) access by random index. -
Use a hash table
d
to map each value to its index in the array - this allows O(1) checking if a value exists and finding its position. -
The removal trick: Instead of removing an element from the middle of the array (which would require shifting all subsequent elements), we swap the element to be removed with the last element in the array. Then we can simply pop the last element in O(1) time. We just need to update the hash table to reflect the new index of the swapped element.
This swap-and-pop technique is the crucial optimization that allows us to maintain O(1) removal while using an array. By always removing from the end, we avoid the costly array reorganization, and the hash table ensures we can quickly find any element's position for the swap.
Solution Approach
The solution uses a combination of a hash table and a dynamic list to achieve O(1) average time complexity for all operations.
Data Structures:
self.d
: A hash table (dictionary) that maps each value to its index in the listself.q
: A dynamic list (array) that stores all the values in the set
Implementation Details:
1. __init__()
Method:
Initialize an empty dictionary d
and an empty list q
.
2. insert(val)
Method:
- First check if
val
exists in the hash tabled
- If it exists, return
False
(value already in set) - Otherwise:
- Get the current length of list
q
as the index for the new element - Store this index in the hash table:
self.d[val] = len(self.q)
- Append
val
to the end of listq
:self.q.append(val)
- Return
True
(successfully inserted)
- Get the current length of list
3. remove(val)
Method:
- First check if
val
exists in the hash tabled
- If it doesn't exist, return
False
(value not in set) - Otherwise:
- Get the index
i
ofval
from the hash table:i = self.d[val]
- Move the last element to position
i
:- Update the hash table entry for the last element:
self.d[self.q[-1]] = i
- Replace the element at position
i
with the last element:self.q[i] = self.q[-1]
- Update the hash table entry for the last element:
- Remove the last element from the list:
self.q.pop()
- Remove
val
from the hash table:self.d.pop(val)
- Return
True
(successfully removed)
- Get the index
4. getRandom()
Method:
- Use Python's
choice()
function to randomly select an element from listq
- Return the selected element
The key optimization is in the remove
operation: instead of removing an element from the middle of the array (which would take O(n) time), we swap it with the last element and then remove from the end (which takes O(1) time). This swap-and-pop technique maintains the O(1) time complexity while keeping all elements accessible for random selection.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a sequence of operations to see how the data structure works:
Initial State:
d = {}
(empty hash table)q = []
(empty array)
Operation 1: insert(10)
- Check if 10 exists in
d
: No - Get index for new element:
len(q) = 0
- Update hash table:
d[10] = 0
- Append to array:
q = [10]
- Return
True
- State:
d = {10: 0}
,q = [10]
Operation 2: insert(20)
- Check if 20 exists in
d
: No - Get index for new element:
len(q) = 1
- Update hash table:
d[20] = 1
- Append to array:
q = [10, 20]
- Return
True
- State:
d = {10: 0, 20: 1}
,q = [10, 20]
Operation 3: insert(30)
- Check if 30 exists in
d
: No - Get index for new element:
len(q) = 2
- Update hash table:
d[30] = 2
- Append to array:
q = [10, 20, 30]
- Return
True
- State:
d = {10: 0, 20: 1, 30: 2}
,q = [10, 20, 30]
Operation 4: remove(20)
(This demonstrates the swap-and-pop trick)
- Check if 20 exists in
d
: Yes, at index 1 - Get index of 20:
i = d[20] = 1
- Move last element (30) to position 1:
- Update hash table for last element:
d[30] = 1
- Replace element at index 1:
q[1] = 30
, soq = [10, 30, 30]
- Update hash table for last element:
- Pop last element:
q = [10, 30]
- Remove 20 from hash table:
d.pop(20)
- Return
True
- State:
d = {10: 0, 30: 1}
,q = [10, 30]
Operation 5: getRandom()
- Randomly select from
q = [10, 30]
- Each element has 50% chance of being selected
- Let's say it returns
30
Operation 6: insert(20)
(Re-inserting a previously removed value)
- Check if 20 exists in
d
: No - Get index for new element:
len(q) = 2
- Update hash table:
d[20] = 2
- Append to array:
q = [10, 30, 20]
- Return
True
- State:
d = {10: 0, 30: 1, 20: 2}
,q = [10, 30, 20]
Operation 7: remove(10)
(Removing the first element)
- Check if 10 exists in
d
: Yes, at index 0 - Get index of 10:
i = d[10] = 0
- Move last element (20) to position 0:
- Update hash table for last element:
d[20] = 0
- Replace element at index 0:
q[0] = 20
, soq = [20, 30, 20]
- Update hash table for last element:
- Pop last element:
q = [20, 30]
- Remove 10 from hash table:
d.pop(10)
- Return
True
- State:
d = {30: 1, 20: 0}
,q = [20, 30]
The key insight demonstrated here is how the remove
operation maintains O(1) complexity by always swapping with the last element before removing, avoiding the need to shift elements in the array.
Solution Implementation
1from random import choice
2
3class RandomizedSet:
4 def __init__(self):
5 """Initialize the data structure."""
6 # Dictionary to store value -> index mapping
7 self.val_to_index = {}
8 # List to store actual values for O(1) random access
9 self.values = []
10
11 def insert(self, val: int) -> bool:
12 """
13 Insert a value to the set. Returns true if the set did not already contain the specified element.
14
15 Args:
16 val: The value to insert
17
18 Returns:
19 True if insertion was successful, False if value already exists
20 """
21 # Check if value already exists
22 if val in self.val_to_index:
23 return False
24
25 # Add value to the end of the list
26 self.val_to_index[val] = len(self.values)
27 self.values.append(val)
28 return True
29
30 def remove(self, val: int) -> bool:
31 """
32 Remove a value from the set. Returns true if the set contained the specified element.
33
34 Args:
35 val: The value to remove
36
37 Returns:
38 True if removal was successful, False if value doesn't exist
39 """
40 # Check if value exists
41 if val not in self.val_to_index:
42 return False
43
44 # Get index of element to remove
45 index_to_remove = self.val_to_index[val]
46 last_element = self.values[-1]
47
48 # Move the last element to the position of element to remove
49 self.values[index_to_remove] = last_element
50 self.val_to_index[last_element] = index_to_remove
51
52 # Remove the last element from list and the value from dictionary
53 self.values.pop()
54 del self.val_to_index[val]
55
56 return True
57
58 def getRandom(self) -> int:
59 """
60 Get a random element from the set.
61
62 Returns:
63 A random value from the set
64 """
65 return choice(self.values)
66
67
68# Your RandomizedSet object will be instantiated and called as such:
69# obj = RandomizedSet()
70# param_1 = obj.insert(val)
71# param_2 = obj.remove(val)
72# param_3 = obj.getRandom()
73
1class RandomizedSet {
2 // HashMap to store value -> index mapping for O(1) lookup
3 private Map<Integer, Integer> valueToIndexMap;
4 // ArrayList to store actual values for O(1) random access
5 private List<Integer> valuesList;
6 // Random number generator for getRandom() method
7 private Random random;
8
9 /**
10 * Initialize the RandomizedSet data structure
11 */
12 public RandomizedSet() {
13 valueToIndexMap = new HashMap<>();
14 valuesList = new ArrayList<>();
15 random = new Random();
16 }
17
18 /**
19 * Inserts a value to the set
20 * @param val - value to insert
21 * @return true if the set did not already contain the specified element
22 */
23 public boolean insert(int val) {
24 // Check if value already exists
25 if (valueToIndexMap.containsKey(val)) {
26 return false;
27 }
28
29 // Add value to the end of the list and store its index in the map
30 valueToIndexMap.put(val, valuesList.size());
31 valuesList.add(val);
32 return true;
33 }
34
35 /**
36 * Removes a value from the set
37 * @param val - value to remove
38 * @return true if the set contained the specified element
39 */
40 public boolean remove(int val) {
41 // Check if value exists
42 if (!valueToIndexMap.containsKey(val)) {
43 return false;
44 }
45
46 // Get the index of the value to be removed
47 int indexToRemove = valueToIndexMap.get(val);
48 int lastElement = valuesList.get(valuesList.size() - 1);
49
50 // Move the last element to the position of the element to be removed
51 valuesList.set(indexToRemove, lastElement);
52 // Update the index of the moved element in the map
53 valueToIndexMap.put(lastElement, indexToRemove);
54
55 // Remove the last element from the list
56 valuesList.remove(valuesList.size() - 1);
57 // Remove the value from the map
58 valueToIndexMap.remove(val);
59
60 return true;
61 }
62
63 /**
64 * Get a random element from the set
65 * @return a random element from the set
66 */
67 public int getRandom() {
68 return valuesList.get(random.nextInt(valuesList.size()));
69 }
70}
71
72/**
73 * Your RandomizedSet object will be instantiated and called as such:
74 * RandomizedSet obj = new RandomizedSet();
75 * boolean param_1 = obj.insert(val);
76 * boolean param_2 = obj.remove(val);
77 * int param_3 = obj.getRandom();
78 */
79
1class RandomizedSet {
2public:
3 RandomizedSet() {
4 // Constructor - no initialization needed as containers are empty by default
5 }
6
7 bool insert(int val) {
8 // Check if value already exists in the set
9 if (valueToIndex.count(val)) {
10 return false;
11 }
12
13 // Add value to the end of the vector and store its index in the map
14 valueToIndex[val] = values.size();
15 values.push_back(val);
16 return true;
17 }
18
19 bool remove(int val) {
20 // Check if value exists in the set
21 if (!valueToIndex.count(val)) {
22 return false;
23 }
24
25 // Get the index of the value to be removed
26 int indexToRemove = valueToIndex[val];
27 int lastValue = values.back();
28
29 // Move the last element to the position of the element to be removed
30 // This maintains O(1) removal by avoiding shifting elements
31 values[indexToRemove] = lastValue;
32 valueToIndex[lastValue] = indexToRemove;
33
34 // Remove the last element from vector and the value from map
35 values.pop_back();
36 valueToIndex.erase(val);
37 return true;
38 }
39
40 int getRandom() {
41 // Return a random element from the vector
42 // All elements have equal probability of being selected
43 return values[rand() % values.size()];
44 }
45
46private:
47 // Hash map to store value -> index mapping for O(1) lookup
48 unordered_map<int, int> valueToIndex;
49
50 // Vector to store actual values and enable O(1) random access
51 vector<int> values;
52};
53
54/**
55 * Your RandomizedSet object will be instantiated and called as such:
56 * RandomizedSet* obj = new RandomizedSet();
57 * bool param_1 = obj->insert(val);
58 * bool param_2 = obj->remove(val);
59 * int param_3 = obj->getRandom();
60 */
61
1// Map to store value -> index in array for O(1) lookup
2const valueToIndexMap: Map<number, number> = new Map();
3
4// Array to store all values for O(1) random access
5const valuesArray: number[] = [];
6
7/**
8 * Inserts a value into the set
9 * @param val - The value to insert
10 * @returns true if the value was inserted, false if it already exists
11 */
12function insert(val: number): boolean {
13 // Check if value already exists
14 if (valueToIndexMap.has(val)) {
15 return false;
16 }
17
18 // Add value to the end of array and store its index in map
19 valueToIndexMap.set(val, valuesArray.length);
20 valuesArray.push(val);
21 return true;
22}
23
24/**
25 * Removes a value from the set
26 * @param val - The value to remove
27 * @returns true if the value was removed, false if it doesn't exist
28 */
29function remove(val: number): boolean {
30 // Check if value exists
31 if (!valueToIndexMap.has(val)) {
32 return false;
33 }
34
35 // Get the index of the value to be removed
36 const indexToRemove = valueToIndexMap.get(val)!;
37 const lastElement = valuesArray[valuesArray.length - 1];
38
39 // Move the last element to the position of the element to be removed
40 valuesArray[indexToRemove] = lastElement;
41 // Update the index mapping for the moved element
42 valueToIndexMap.set(lastElement, indexToRemove);
43
44 // Remove the last element from array and delete the value from map
45 valuesArray.pop();
46 valueToIndexMap.delete(val);
47 return true;
48}
49
50/**
51 * Returns a random element from the set
52 * @returns A random value from the set
53 */
54function getRandom(): number {
55 // Generate random index and return the corresponding value
56 const randomIndex = Math.floor(Math.random() * valuesArray.length);
57 return valuesArray[randomIndex];
58}
59
Time and Space Complexity
Time Complexity: O(1)
for all operations (insert
, remove
, and getRandom
)
-
insert(val)
: Checking ifval
exists in the dictionary isO(1)
. Getting the length of the list isO(1)
. Adding to the dictionary and appending to the list are bothO(1)
operations. -
remove(val)
: Checking dictionary membership isO(1)
. Accessing elements by index in both the dictionary and list isO(1)
. Swapping the element to be removed with the last element isO(1)
. Thepop()
operation on the list (removing the last element) isO(1)
. Dictionary deletion withpop(val)
is alsoO(1)
. -
getRandom()
: Thechoice()
function from the random module selects a random element from the list inO(1)
time.
Space Complexity: O(n)
, where n
is the number of elements in the set
- The dictionary
self.d
storesn
key-value pairs (value to index mappings), requiringO(n)
space. - The list
self.q
storesn
elements, requiringO(n)
space. - Total space complexity is
O(n) + O(n) = O(n)
.
Common Pitfalls
1. Incorrect Index Update During Removal
The most common mistake is forgetting to handle the edge case when removing the last element from the array. When the element to be removed is already the last element, the swap operation would incorrectly update its own index in the hash table.
Problematic Code:
def remove(self, val: int) -> bool:
if val not in self.val_to_index:
return False
index_to_remove = self.val_to_index[val]
last_element = self.values[-1]
# BUG: This fails when removing the last element
self.values[index_to_remove] = last_element
self.val_to_index[last_element] = index_to_remove
self.values.pop()
del self.val_to_index[val]
return True
Solution: Add a check to handle when the element to remove is the last element:
def remove(self, val: int) -> bool:
if val not in self.val_to_index:
return False
index_to_remove = self.val_to_index[val]
last_element = self.values[-1]
# Only update if we're not removing the last element
if index_to_remove != len(self.values) - 1:
self.values[index_to_remove] = last_element
self.val_to_index[last_element] = index_to_remove
self.values.pop()
del self.val_to_index[val]
return True
2. Order of Operations in Remove
Another pitfall is deleting the value from the hash table before updating the last element's index, which causes a KeyError when the element to remove is the last element.
Problematic Code:
def remove(self, val: int) -> bool:
if val not in self.val_to_index:
return False
index_to_remove = self.val_to_index[val]
last_element = self.values[-1]
# BUG: Deleting too early
del self.val_to_index[val]
# This fails if val == last_element
self.values[index_to_remove] = last_element
self.val_to_index[last_element] = index_to_remove # KeyError here!
self.values.pop()
return True
Solution: Always delete from the hash table after all other operations are complete.
3. Using Wrong Random Selection Method
Using random.randint()
incorrectly or implementing custom random selection can lead to index out of bounds errors.
Problematic Code:
import random
def getRandom(self) -> int:
# BUG: randint is inclusive on both ends, could cause IndexError
index = random.randint(0, len(self.values))
return self.values[index]
Solution:
Use random.choice()
which handles the bounds correctly, or use random.randint(0, len(self.values) - 1)
with proper bounds.
from random import choice
def getRandom(self) -> int:
return choice(self.values)
Which algorithm should you use to find a node that is close to the root of the tree?
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!