Algorithm

Insert Delete GetRandom O(1)

Design Pattern

Insert Delete GetRandom O(1)

Design a set-like structure, RandomizedSet, where all three operations run in average O(1) time. insert(val) adds val if it is not already present and returns true; if val is already there it returns false and changes nothing. remove(val) deletes val if present and returns true, otherwise returns false. getRandom() returns one of the currently stored elements chosen uniformly at random — every element must have exactly the same chance of being picked.

CONSTRAINTS
  • -2³¹ <= val <= 2³¹ - 1
  • getRandom is only called when the set has at least one element
  • At most 2 × 10⁵ calls will be made across insert, remove, and getRandom
EXAMPLE 1
Input: insert(1), remove(2), insert(2), getRandom()
Output: true, false, true, 2
insert(1) succeeds → true. remove(2) finds nothing (2 isn't present) → false. insert(2) succeeds → true. The set is now {1, 2}, so getRandom returns 1 or 2 with equal probability.
EXAMPLE 2
Input: insert(5), insert(5), remove(5), remove(5)
Output: true, false, true, false
The second insert(5) is a duplicate → false. The first remove(5) deletes it → true. The second remove(5) finds nothing left → false. Return values pin down present-vs-absent at each step.
Can getRandom be called on an empty set?
No — the constraints guarantee at least one element whenever getRandom is called, so you don't need to handle an empty pick. Worth confirming in an interview, since otherwise you'd return an error or null.
Does 'uniformly at random' really require equal probability?
Yes. Every currently-stored element must have exactly a 1/N chance. Packing values into a gap-free array and picking a random index in [0, N) is what guarantees that; a hash map's scattered layout can't.
Can the set hold duplicate values?
No — it's a set, so insert rejects a value already present (returning false). The follow-up 'Insert Delete GetRandom O(1) - Duplicates allowed' relaxes this by mapping each value to a set of indices instead of a single one.
Why swap with the last element instead of shifting?
Shifting to close the gap is O(N). Swapping the target with the last element and popping keeps the deletion O(1), and it's safe precisely because a set has no meaningful order to disturb.

Three requirements, and each one alone points at a different data structure — the trick is that no single structure satisfies all three, so we have to combine two. Let's see why each pulls a different way.

getRandom wants an array; remove wants a hash map

Look at getRandom first. To pick a uniformly random element in O(1), you need your elements packed into consecutive slots 0, 1, 2, … — an array — so you can generate one random index and read that slot instantly. A hash map can't do this: its entries are scattered across buckets with gaps, so there is no clean "pick index between 0 and N-1" move without walking it.

Now look at insert and remove, which must reject duplicates and delete by value. To ask "is val already here?" in O(1) you need a hash map — the very lookup-by-identity you built in Design HashMap. An array can't do that: finding a value in an array means scanning it, O(N).

So the two operations demand opposite structures. The resolution isn't to pick one — it's to keep both, each covering the other's weakness, kept perfectly in sync.

The pairing: array of values + map from value to its index

Store the actual elements in an array nums. Alongside it keep a hash map pos that records, for every value, which index of nums it sits at (value → index). Insert appends to the array and records the new index in the map — both O(1). getRandom picks a random index into nums — O(1). The only genuinely hard one is remove, and it has a beautiful fix.

The load-bearing trick: delete by swapping with the last

Deleting from the middle of an array is normally O(N), because every element after the hole must slide left to close the gap. But here we don't care about order — it's a set, order is meaningless. That freedom unlocks an O(1) deletion: instead of removing from the middle, swap the doomed element with the last element, then chop off the last slot. The last slot is always free to remove in O(1) (nothing sits after it), and the element that filled the hole just needs its new index recorded in the map.

python
import random

class RandomizedSet:
    def __init__(self):
        self.nums = []     # the values, packed 0..N-1 for getRandom
        self.pos = {}      # value -> its current index in nums

    def insert(self, val):
        if val in self.pos:        # O(1) duplicate check
            return False
        self.pos[val] = len(self.nums)
        self.nums.append(val)
        return True

    def remove(self, val):
        if val not in self.pos:
            return False
        idx = self.pos[val]        # where val lives
        last = self.nums[-1]       # the element we'll move into its place
        self.nums[idx] = last      # overwrite the hole with the last element
        self.pos[last] = idx       # tell the map where 'last' moved to
        self.nums.pop()            # drop the now-duplicate final slot (O(1))
        del self.pos[val]          # forget val
        return True

    def getRandom(self):
        return self.nums[random.randrange(len(self.nums))]
Crucial Noteupdate the map before you pop, and handle the case where the element being removed is the last one. If val is already the last element, last equals val: you overwrite slot idx with itself, set pos[val] = idx (harmless), then pop and del pos[val] — correct. The subtle bug is ordering: if you del pos[val] first and val == last, you'd then re-add it via pos[last] = idx, resurrecting a value you just deleted. Overwrite-and-relocate first, delete the target last.
Worked Example:watch the swap relocate an index
- insert(10)nums = [10], pos = {10: 0}, return true.
- insert(20)nums = [10, 20], pos = {10: 0, 20: 1}, return true.
- insert(30)nums = [10, 20, 30], pos = {10: 0, 20: 1, 30: 2}.
- insert(20) (non-happy path) → 20 is already in pos → return false, nothing changes.
- remove(10) → 10 is at index 0. Last element is 30. Overwrite slot 0 with 30 → nums = [30, 20, 30]; record pos[30] = 0; pop the final slot → nums = [30, 20]; delete 10 → pos = {30: 0, 20: 1}. The array stayed packed, 30 simply took the freed spot.
- getRandom() → random index 0 or 1, each returning 30 or 20 with equal 50% odds.
What the pairing bought, and the reflex

Every operation is now O(1) average: the map answers "is it here / where is it" instantly, and the swap-with-last turns middle deletion into end deletion. The cost is O(N) memory for holding each element twice-over (once as a value, once as a map entry) — the same spend memory to buy speed trade from Two Sum, now doubled. The transferable lesson is bigger than this one problem: when two operations demand structures that conflict, keep both and maintain an invariant that links them — here, "pos[v] always equals the current index of v in nums." Every line of both methods exists to preserve that one sentence. And the swap-with-last move is worth memorizing on its own: it is the standard way to delete from an array in O(1) whenever order doesn't matter.

Interactive Strategy Visualization
O(N) Array Middle-Delete
O(1) Average Array + Hash Map with Swap-and-Pop