2Brute2Best
πŸ”₯0

Insert Delete GetRandom O(1)

MediumΒ·LeetCode #380Basic Anchor
LeetCode
30-Second Mental ModelDesign with hash maps (vector + map value β†’ index)
Complexity:O(N)β†’O(1)
Key Insight

Keep two structures in sync. vals holds every value packed with no gaps, so getRandom is vals[rand() % size]. pos maps each value to its index in vals, so we can find any value in O(1). To remove val at index i, move the last value into slot i, update that moved value's index in pos, and pop_back. Nothing shifts.

Core Validity Invariant

The two structures always agree: for every stored value v, vals[pos[v]] == v, and vals has no gaps. Every operation must leave this true.

01

Problem

Build a class RandomizedSet that stores a set of distinct integers and supports three operations, each in O(1) average time:

  • bool insert(int val): add val if it is not already there. Return true if it was added, false if it was already present.
  • bool remove(int val): delete val if it is there. Return true if it was removed, false if it was not present.
  • int getRandom(): return one of the stored values, chosen at random. Every stored value must have the same chance of being returned.
Input

A sequence of calls on one RandomizedSet object: insert(val), remove(val) and getRandom().

Output

The return value of each call: true/false for insert and remove, and a stored value for getRandom.

Example 1
Input: insert(1), remove(2), insert(2), getRandom(), remove(1), insert(2), getRandom()
Output: true, false, true, 1 or 2, true, false, 2

1 is added. 2 is not there, so remove(2) fails. 2 is added, so the set is {1, 2} and getRandom returns 1 or 2 with equal chance. 1 is removed. 2 is already there, so insert(2) fails. Now the set is {2}, so getRandom must return 2.

Constraints
  • β€’ βˆ’2^31 ≀ val ≀ 2^31 βˆ’ 1
  • β€’ At most 2 Β· 10^5 calls in total
  • β€’ getRandom is only called when the set has at least one value
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck β€” each hint is counted.

  1. 1.

    Which container gives me a random element in O(1)? Which one tells me "is x here?" in O(1)? Are they the same container?

  2. 2.

    If I keep the values in a vector, what exactly is slow about removing one from the middle?

  3. 3.

    Does the order of the values in the set matter at all?

  4. 4.

    If order does not matter, is there a cheap way to delete from a vector without shifting anything?

  5. 5.

    If I move a value to a new index, what else must I update so I can still find it later?

03

Brute force approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForceKeep values in a vector; insert and remove search it linearlyO(N)O(N)
OptimalVector for random access, map for O(1) lookup; delete by swapping with the last valueO(1)O(N)
09

Why does this belong to this pattern?

Clues in the problem statement
  • β€œeach operation in O(1)”No scanning allowed. Every operation needs a direct lookup β†’ hash map.
  • β€œrandom element, equal chance”Needs index-based access with no gaps β†’ a packed vector.
  • β€œset (order does not matter)”We may rearrange the vector freely β†’ delete by swapping with the last element.
O(1) find by value+O(1) access by random index+order does not matter→Design with hash maps (vector + map value → index)

This is the anchor of the design sub-pattern: no single container is fast at everything, so we pair a hash map with a second structure and keep them in sync. Here the second structure is a vector, and the trick that keeps it packed is "swap with last, then pop". LRU Cache uses the same idea with a linked list instead, because there the order matters.

10

Don't make this mistake

Forgetting to update the moved value's index
βœ— int i = pos[val]; vals[i] = vals.back(); vals.pop_back(); pos.erase(val);
βœ“ int i = pos[val], last = vals.back(); vals[i] = last; pos[last] = i; // the moved value has a new home vals.pop_back(); pos.erase(val);

After the swap, last lives at index i, but the map still says it is at the old last index, which no longer exists. The next remove(last) will read the wrong slot and corrupt the set.

Erasing from the map before updating the moved value
βœ— pos.erase(val); vals[i] = last; pos[last] = i; vals.pop_back();
βœ“ vals[i] = last; pos[last] = i; vals.pop_back(); pos.erase(val); // erase last

When val is itself the last value, last == val. Erasing first and then writing pos[last] = i puts val back into the map, even though it was removed. Later insert(val) returns false by mistake. Updating first and erasing last is correct in both cases.

Using vector::erase in the middle
βœ— vals.erase(vals.begin() + pos[val]);

This shifts every later element, so remove becomes O(N), and every shifted value's index in the map is now wrong too. Swap with the last element instead; a set's order does not matter.

Using an unordered_set alone
βœ— unordered_set<int> s; int getRandom() { auto it = s.begin(); advance(it, rand() % s.size()); // O(N) walk return *it; }

Pattern-confusion trap. A hash set is the obvious tool for "insert / remove / contains", but it has no index. Reaching the k-th element means walking the iterator, which is O(N). The design pattern here is a map plus an array, so that random access by index is possible.

11

Want to implement this?

Write it yourself on LeetCode, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like Arrays & Hashing translate into production architectures and low-level memory mechanics.