248. Design Randomized Set
Problem
Implement RandomizedSet for distinct hashable values with insert(value), remove(value), and getRandom(). Insert/remove return whether membership changed. getRandom returns a uniformly selected current member and raises IndexError when empty. Operations should take expected amortized constant time; cryptographic randomness is not required.
Examples
Example 1
run_operations(RandomizedSet,[("insert",1),("insert",2),("remove",1),("getRandom",),("remove",1)])
=> [True, True, True, 2, False]After removal only 2 can be sampled.
Example 2
run_operations(RandomizedSet,[("insert",7),("insert",7),("remove",7)])
=> [True, False, True]Duplicate insertion leaves membership unchanged.
Example 3
store=RandomizedSet(); store.insert(1); store.insert(2); store.insert(3); store.remove(2)
(store.values,store.positions)
=> ([1, 3], {1: 0, 3: 1})The moved final value receives the vacated index.
Reveal the worked solution and reasoning
Why this mechanism works
A list gives constant-time random indexing but slow arbitrary deletion. A dictionary locates any value's index. Delete by moving the list's final value into the removed slot, updating that moved value's index, then popping the last slot. Set semantics impose no order to preserve.
State and boundaries
values contains each member once, and positions[value] is exactly its current list index. Both structures represent the same membership after every operation.
Protocol
- Insert absent values at the list end and map their index.
- For removal locate the slot.
- Move the final value into it and update that value's mapping.
- Pop the list end and delete the removed mapping.
- Choose a uniform random list entry.
Visualization
values [A,B,C], positions A:0 B:1 C:2
remove B -> move C into slot 1 -> [A,C]
repair C:1, delete BPython implementation
import random
from structures import run_operations
class RandomizedSet:
def __init__(self):
self.values = []
self.positions = {}
def insert(self, value):
if value in self.positions:
return False
self.positions[value] = len(self.values)
self.values.append(value)
return True
def remove(self, value):
if value not in self.positions:
return False
index = self.positions[value]
last = self.values[-1]
self.values[index] = last
self.positions[last] = index
self.values.pop()
del self.positions[value]
return True
def getRandom(self):
return random.choice(self.values)
Worked execution
run_operations(RandomizedSet,[("insert",1),("insert",2),("remove",1),("getRandom",),("remove",1)])
L6 __init__: self.values = []
self=RandomizedSet{'values':[]}
L7 __init__: self.positions = {}
self=RandomizedSet{'values':[], 'positions':{}}
L10 insert: if value in self.positions:
self=RandomizedSet{'values':[], 'positions':{}}; value=1
L12 insert: self.positions[value] = len(self.values)
self=RandomizedSet{'values':[], 'positions':{...:...}}; value=1
L13 insert: self.values.append(value)
self=RandomizedSet{'values':[...], 'positions':{...:...}}; value=1
L10 insert: if value in self.positions:
self=RandomizedSet{'values':[...], 'positions':{...:...}}; value=2
L12 insert: self.positions[value] = len(self.values)
self=RandomizedSet{'values':[...], 'positions':{...:..., ...:...}}; value=2
L13 insert: self.values.append(value)
self=RandomizedSet{'values':[..., ...], 'positions':{...:..., ...:...}}; value=2
... 3 intermediate state changes omitted ...
L23 remove: self.values.pop()
self=RandomizedSet{'values':[...], 'positions':{...:..., ...:...}}; value=1; index=0; last=2
L24 remove: del self.positions[value]
self=RandomizedSet{'values':[...], 'positions':{...:...}}; value=1; index=0; last=2
L28 getRandom: return random.choice(self.values)
self=RandomizedSet{'values':[...], 'positions':{...:...}}
L17 remove: if value not in self.positions:
self=RandomizedSet{'values':[...], 'positions':{...:...}}; value=1Result: [True, True, True, 2, False]. After removal only 2 can be sampled.
run_operations(RandomizedSet,[("insert",7),("insert",7),("remove",7)])
L6 __init__: self.values = []
self=RandomizedSet{'values':[]}
L7 __init__: self.positions = {}
self=RandomizedSet{'values':[], 'positions':{}}
L10 insert: if value in self.positions:
self=RandomizedSet{'values':[], 'positions':{}}; value=7
L12 insert: self.positions[value] = len(self.values)
self=RandomizedSet{'values':[], 'positions':{...:...}}; value=7
L13 insert: self.values.append(value)
self=RandomizedSet{'values':[...], 'positions':{...:...}}; value=7
L10 insert: if value in self.positions:
self=RandomizedSet{'values':[...], 'positions':{...:...}}; value=7
L17 remove: if value not in self.positions:
self=RandomizedSet{'values':[...], 'positions':{...:...}}; value=7
L19 remove: index = self.positions[value]
self=RandomizedSet{'values':[...], 'positions':{...:...}}; value=7; index=0
L20 remove: last = self.values[-1]
self=RandomizedSet{'values':[...], 'positions':{...:...}}; value=7; index=0; last=7
L23 remove: self.values.pop()
self=RandomizedSet{'values':[], 'positions':{...:...}}; value=7; index=0; last=7
L24 remove: del self.positions[value]
self=RandomizedSet{'values':[], 'positions':{}}; value=7; index=0; last=7Result: [True, False, True]. Duplicate insertion leaves membership unchanged.
Why the solution is correct
Insertion adds one new aligned entry. Swap-delete removes exactly the requested member and repairs the sole other index that may change; removing the final element also works because its temporary mapping is deleted afterward. Every member occupies one list slot, so uniform index selection gives uniform member selection.
Time and space
Expected amortized O(1) per operation using hashing, dynamic-array append/pop and random indexing; O(n) stored entries. Hash/equality costs depend on the value type.
Common mistakes
Forgetting to update the moved value's index corrupts later removals. Sampling dictionary iteration positions requires linear work unless a separate indexable structure is maintained.
Reasoning card
State: values contains each member once, and positions[value] is exactly its current list index. Both structures represent the same membership after every operation.
Invariant: Map indices and array positions agree after every operation.
Decision to rehearse: Combine an array with value-to-index mapping; swap-delete.
Finish: Choose a uniform random list entry
Transfer exercise
Allow duplicates with a value-to-set-of-indices map, and state how sampling should weight repeated occurrences.