Implement a Snapshot Set Iterator
Company: Databricks
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates data structure design, versioning, and iterator consistency concepts within the Coding & Algorithms domain, focusing on implementing a mutable set that supports snapshot iterators.
Constraints
- 1 <= len(operations) <= 2 * 10^5
- Keys are integers in the range [-10^9, 10^9]
- All iterator ids used by `has_next` and `next` are valid
- `next(iterator_id)` is only called when that iterator still has at least one remaining key
Examples
Input: (['add','add','iterator','remove','add','contains','has_next','next','has_next','next','has_next','iterator','next','contains'], [[1],[2],[],[2],[1],[2],[0],[0],[0],[0],[0],[],[1],[1]])
Expected Output: [None, None, 0, None, None, False, True, 1, True, 2, False, 1, 1, True]
Explanation: Iterator 0 freezes the snapshot {1, 2}. Removing 2 from the live set and re-adding 1 do not change iterator 0. A later iterator 1 sees the current set {1}.
Input: (['iterator','has_next','add','add','remove','remove','iterator','has_next'], [[],[0],[5],[5],[7],[5],[],[1]])
Expected Output: [0, False, None, None, None, None, 1, False]
Explanation: The first iterator is created on an empty set. Adding a duplicate and removing a missing key are both no-ops. After removing 5, the set is empty again, so the second iterator is also empty.
Input: (['add','add','add','iterator','remove','iterator','add','contains','has_next','next','next','next','has_next','has_next','next','next','has_next','iterator','next','next','next'], [[3],[1],[2],[],[2],[],[0],[2],[0],[0],[0],[0],[0],[1],[1],[1],[1],[],[2],[2],[2]])
Expected Output: [None, None, None, 0, None, 1, None, False, True, 1, 2, 3, False, True, 1, 3, False, 2, 0, 1, 3]
Explanation: Iterator 0 sees {1, 2, 3}. After removing 2, iterator 1 sees {1, 3}. After adding 0, iterator 2 sees {0, 1, 3}. Each iterator keeps its own frozen view.
Input: (['add','add','iterator','remove','add','iterator','contains','next','next','has_next','next','has_next','has_next','next','has_next'], [[-1],[4],[],[-1],[2],[],[-1],[0],[1],[0],[0],[0],[1],[1],[1]])
Expected Output: [None, None, 0, None, None, 1, False, -1, 2, True, 4, False, True, 4, False]
Explanation: Negative keys must still be returned in ascending order. Iterator 0 sees {-1, 4}, while iterator 1 sees {2, 4} after the mutation.
Hints
- Copying the whole set into every new iterator works, but it can be too slow when snapshots are frequent. Think about storing immutable versions of the set instead.
- If you define iteration order as sorted order, coordinate compression plus a persistent segment tree can support updates and snapshot iteration efficiently.