Facebook Pixel

380. Insert Delete GetRandom O(1)

MediumDesignArrayHash TableMathRandomized
Leetcode Link

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 integer val to the set if it's not already present. Returns true if the value was successfully added (wasn't in the set before), and false if the value was already in the set.

  • remove(val): Removes the integer val from the set if it exists. Returns true if the value was successfully removed (was in the set), and false 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Use an array q to store all values - this makes getRandom trivial with O(1) access by random index.

  2. 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.

  3. 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 list
  • self.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 table d
  • 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 list q: self.q.append(val)
    • Return True (successfully inserted)

3. remove(val) Method:

  • First check if val exists in the hash table d
  • If it doesn't exist, return False (value not in set)
  • Otherwise:
    • Get the index i of val 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]
    • Remove the last element from the list: self.q.pop()
    • Remove val from the hash table: self.d.pop(val)
    • Return True (successfully removed)

4. getRandom() Method:

  • Use Python's choice() function to randomly select an element from list q
  • 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 Evaluator

Example 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, so q = [10, 30, 30]
  • 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, so q = [20, 30, 20]
  • 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 if val exists in the dictionary is O(1). Getting the length of the list is O(1). Adding to the dictionary and appending to the list are both O(1) operations.

  • remove(val): Checking dictionary membership is O(1). Accessing elements by index in both the dictionary and list is O(1). Swapping the element to be removed with the last element is O(1). The pop() operation on the list (removing the last element) is O(1). Dictionary deletion with pop(val) is also O(1).

  • getRandom(): The choice() function from the random module selects a random element from the list in O(1) time.

Space Complexity: O(n), where n is the number of elements in the set

  • The dictionary self.d stores n key-value pairs (value to index mappings), requiring O(n) space.
  • The list self.q stores n elements, requiring O(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)
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More