Problem Overview: Design RandomizedSet in O(1)
LeetCode 380 asks you to design a data structure called RandomizedSet that supports three operations: insert(val) which inserts val if not present and returns true, otherwise returns false; remove(val) which removes val if present and returns true, otherwise returns false; and getRandom() which returns a random element from the current set, with each element equally likely to be chosen.
The key constraint is that all three operations must run in average O(1) time. This rules out naive implementations that use only a single data structure. Understanding why each structure alone fails — and how to combine them — is the core insight of this problem.
This is a classic data structure design problem that appears frequently in FAANG interviews. It tests your ability to combine multiple data structures to achieve performance guarantees that no single structure can provide on its own.
- insert(val): insert val if not present, return true/false
- remove(val): remove val if present, return true/false
- getRandom(): return any element uniformly at random
- All operations must run in average O(1) time
- No duplicate values in the set
- At least one element exists when getRandom() is called
Why HashMap or Array Alone Fails
A HashMap alone handles insert and remove in O(1), but getRandom breaks down. To pick a random element you need to convert the key set into a list or iterate through it, which takes O(n) time. There is no way to index into a HashMap by position, so you cannot directly pick the kth element.
An array (or list) alone makes getRandom trivial — just pick a random index in O(1). But remove is O(n) because you have to search for the element and then shift all subsequent elements left to fill the gap. Even with a HashSet you can check membership in O(1), but the actual deletion from the underlying array is still O(n).
Neither structure alone satisfies all three requirements. The solution is to use both together: a HashMap for O(1) lookup and deletion bookkeeping, and an array for O(1) random access. A clever swap trick lets you remove from the array in O(1) by eliminating the costly shift.
Key Insight
Combine both: use a HashMap (val → index) for O(1) membership checks and remove bookkeeping, and a dynamic array for O(1) random access via index. The swap-to-back trick bridges the gap for O(1) deletion.
The Swap-to-Back Trick for O(1) Array Deletion
The classic problem with removing from an array is that you must shift all elements after the removed element one position left — that is O(n). The swap-to-back trick eliminates this entirely by swapping the target element with the last element in the array and then popping the last position.
After the swap, the element that was last is now at the removed element's old index. You must update the HashMap to reflect its new index. Then you pop the last position — a true O(1) operation. The element you wanted to remove is gone, and no shifting occurred.
This trick only works because the set has no notion of order. If the problem required maintaining insertion order or sorted order, swapping would violate that invariant. RandomizedSet only needs uniform random access, so order within the array does not matter at all.
- 1Find the index of val to remove using the HashMap
- 2Swap array[index] with array[last] (the last element)
- 3Update the HashMap for the swapped element: map[last_val] = index
- 4Pop the last element from the array (O(1))
- 5Delete val from the HashMap
- 6Return true
Insert Operation: Append and Record Index
The insert operation is straightforward. First check if val is already in the HashMap — if it is, return false immediately since the set does not allow duplicates. If val is not present, append it to the end of the array and record its index in the HashMap.
Appending to the end of a dynamic array (like Python list or Java ArrayList) is amortized O(1). Recording the index in the HashMap is O(1). So the entire insert operation is O(1) amortized.
The HashMap maps each value to its current index in the array. This mapping is what makes O(1) removal possible — you can jump directly to any element's position without scanning the array.
HashMap Role
The HashMap stores val → index in array. This enables O(1) lookup of an element's position, which is exactly what the swap-to-back trick needs to jump directly to the element for removal.
Remove Operation: Swap, Pop, and Delete
The remove operation uses the swap-to-back trick. Start by checking if val exists in the HashMap — if not, return false. If it does, retrieve its current index. Swap the element at that index with the last element in the array, then update the HashMap for the swapped element to reflect its new index.
Next, pop the last element of the array (which is now val after the swap) in O(1), and delete val from the HashMap. Return true. Every step is O(1): the HashMap lookup, the array swap, the HashMap update, the array pop, and the HashMap delete.
One subtle edge case: if val happens to be the last element already, then swapping it with itself is a no-op, and you just pop and delete. The logic handles this naturally without any special casing as long as you update the HashMap correctly before or after.
- 1Check if val is in the HashMap; if not, return false
- 2Get idx = map[val]
- 3Get last_val = array[last]
- 4Set array[idx] = last_val
- 5Update map[last_val] = idx
- 6Pop array[last]
- 7Delete map[val]
- 8Return true
Code Walkthrough: Python and Java
In Python, the implementation uses a list for the array and a dict for the HashMap. The list supports O(1) append and O(1) pop from the end. The dict provides O(1) average lookup and deletion. For getRandom, Python's random.choice(self.vals) picks a uniform random element in O(1) since list indexing is O(1).
In Java, use an ArrayList for the array and a HashMap for the mapping. ArrayList.add() and ArrayList.remove(size-1) are O(1) amortized. For getRandom, use new Random().nextInt(list.size()) to generate a random index and return list.get(index). Both implementations follow the exact same logic — the language choice does not affect the algorithm.
The overall space complexity is O(n) for both the array and the HashMap, where n is the number of elements currently in the set. The time complexity is O(1) average for all three operations due to the amortized O(1) behavior of dynamic array append and the O(1) average performance of hash operations.
Order Matters for Last Element
When removing the last element (val is already at the last index), the swap is a no-op. Be careful: you must delete val from the map before or after popping — if you update map[last_val] = idx first and val equals last_val, you may overwrite an entry you are about to delete. Write the delete step last to be safe.