Multiset with O(1) Insert, Remove, and Count-Weighted Random Pick
SnowflakeSoftware Engineer · OnsiteLast updated
Design a collection of integers that allows duplicates and supports insert, remove and a random pick weighted by occurrence count, each in average constant time. Tests array and hash map bookkeeping, careful handling of moved elements during removal, and how to test randomized behavior.
Design a data structure that holds a collection of integers in which duplicates are allowed, and that supports each of the following operations in average O(1) time:
insert(val) -> bool: add one occurrence ofval. ReturnTrueifvalwas not in the collection before this call, andFalseotherwise.remove(val) -> bool: ifvalis present, remove one occurrence of it and returnTrue; otherwise returnFalse.get_random() -> int: return a random element of the collection, where the probability of returning a value is proportional to its number of occurrences. If the collection holds[1, 1, 2],get_randomreturns1with probability 2/3 and2with probability 1/3.
Write the class, then write a few test cases that exercise it.
Constraints
- Values fit in a 32-bit signed integer:
-2**31 <= val <= 2**31 - 1. - At most
2 * 10**5calls are made in total. get_randomis called only when the collection is not empty.
Clarifying Questions
- Is average (expected) O(1) acceptable, or must every operation be O(1) in the worst case?
- Should the random source be injectable, so that tests can be deterministic?
- Must the structure be safe to use from several threads at once?
Hints
Hint 1
Constant-time random pick: Sampling in proportion to occurrence counts is trivial for one kind of container if every occurrence is stored separately. Think about which container that is, and what then makes `remove` hard.
Hint 2
Finding an occurrence: To remove one occurrence of a value quickly, you need to know where at least one of its copies is stored, and to keep that knowledge correct whenever something else moves.
Follow-up questions
- How would you change the structure so that `get_random` picks each distinct value with equal probability, regardless of its count?
- How would you test that `get_random` really returns values in proportion to their counts?
- What changes if several threads call these methods concurrently?
- How would you add `remove_all(val)`, and what would it cost?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.