Insert Delete GetRandom O(1)
Insert Delete GetRandom O(1): design a set with average O(1) insertion, removal, and uniform random selection.
- -2³¹ <= val <= 2³¹ - 1
- At most 2 * 10⁵ calls will be made to insert, remove, and getRandom.
- There will be at least one element in the data structure when getRandom is called.
Intuition
Insert delete getrandom o 1 requires a structure supporting insert, remove, and uniform random selection, all in average O(1). No single built-in structure provides all three, which is the whole point. A hash set gives O(1) insert and remove but cannot pick a uniform random element — its storage has no index. An array gives O(1) random access by index but O(n) removal, since deleting from the middle shifts everything after it. Combining them covers both weaknesses: - Keep an array of the values and a map from each value to its index in that array. Random selection then picks a random array index directly. Insertion appends to the array and records the new index in the map. Removal is the operation that makes this work, and it turns on one trick: Swap the element being removed with the last element, then pop the array. Removing from the end is O(1), and the swap moves the doomed element there. The map must then be updated so the moved element points at its new index. The order matters — update the moved element's index in the map, then delete the removed value's entry. Reversing those steps corrupts the map when the removed element is the last one, since the swap is then a no-op and the update would resurrect a deleted key. The array order is destroyed by these swaps, which is acceptable because the problem never requires order — only membership and uniform sampling.
Constant-time membership and deletion suggest hashing, while uniform random selection suggests a dense indexable array. When both are required, store value-to-index mappings and use swap-delete to prevent gaps and shifts.
Approach
Before reading on: price up what counting everything costs here, then ask what you would need to remember from the left to avoid re-scanning. Aim for O(1) average per operation time and O(n) space.
See why one structure fails
A hash set cannot sample uniformly, having no indices; an array cannot remove from the middle in O(1). Each structure covers the other's weakness.
Pair an array with an index map
Store values in an array and map each value to its index in that array. The array serves random access, the map serves lookup.
Insert by appending
Append the value to the array and record its index in the map. Both operations are O(1), and duplicates are rejected by checking the map first.
Remove by swapping to the end
Swap the target with the last element, then pop. Removing from the end is O(1), and the swap puts the doomed element there.
Update the map in the right order
Point the moved element at its new index before deleting the removed value's entry. Reversing this corrupts the map when the removed element is already last, since the swap is then a no-op.
Sample a random index
Pick a uniform random index into the array and return that value. Every element occupies exactly one slot, so the distribution is uniform.
Cost of the operations
All three run in average O(1), with O(n) space. Array order is destroyed by the swaps, which is acceptable since only membership and sampling are required.
Solution & live demo
Common pitfalls
Using linear array removal
self.values.remove(val)
self.values[index] = last_value
list.remove searches and shifts elements, violating the average O(1) requirement.
Leaving the moved value's old index
self.index[last_value] = len(self.values) - 1
self.index[last_value] = index
After the swap, the last value occupies the removed value's former slot. Keeping its tail index corrupts the next removal.
Sampling the map instead of the array
return random.choice(self.index)
return random.choice(self.values)
The array is the dense random-access structure; random.choice does not operate on a dictionary as a value sequence.
Edge cases
The value is copied onto its own index, popped, and removed from the map, leaving both structures empty.
The map check returns false before either structure changes, so no duplicate array slot is created.
The method returns false immediately and leaves the array-map invariant untouched.