You have a set which contains all positive integers [1, 2, 3, 4, 5, ...].
Implement the SmallestInfiniteSet class:
SmallestInfiniteSet() Initializes the SmallestInfiniteSet object to contain all positive integers.int popSmallest() Removes and returns the smallest integer contained in the infinite set.void addBack(int num) Adds a positive integer num back into the infinite set, if it is not already in the infinite set.Example 1:
Input
["SmallestInfiniteSet", "addBack", "popSmallest", "popSmallest", "popSmallest", "addBack", "popSmallest", "popSmallest", "popSmallest"]
[[], [2], [], [], [], [1], [], [], []]
Output
[null, null, 1, 2, 3, null, 1, 4, 5]
Explanation
SmallestInfiniteSet smallestInfiniteSet = new SmallestInfiniteSet();
smallestInfiniteSet.addBack(2); // 2 is already in the set, so no change is made.
smallestInfiniteSet.popSmallest(); // return 1, since 1 is the smallest number, and remove it from the set.
smallestInfiniteSet.popSmallest(); // return 2, and remove it from the set.
smallestInfiniteSet.popSmallest(); // return 3, and remove it from the set.
smallestInfiniteSet.addBack(1); // 1 is added back to the set.
smallestInfiniteSet.popSmallest(); // return 1, since 1 was added back to the set and
// is the smallest number, and remove it from the set.
smallestInfiniteSet.popSmallest(); // return 4, and remove it from the set.
smallestInfiniteSet.popSmallest(); // return 5, and remove it from the set.
Constraints:
1 <= num <= 10001000 calls will be made in total to popSmallest and addBack.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force strategy imagines an infinite line of all positive numbers starting from one. To find the smallest available number, it walks down this line from the beginning, checking each number one by one to see if it has been previously taken.
Here's how the algorithm would work step-by-step:
class SmallestInfiniteSet:
def __init__(self):
self.next_available_positive_integer = 1
self.returned_numbers_collection = []
def popSmallest(self):
smallest_number_candidate = self.next_available_positive_integer
# We must exhaustively check the returned numbers in case a smaller one was added back.
for number in self.returned_numbers_collection:
if number < smallest_number_candidate:
smallest_number_candidate = number
# This logic correctly updates state based on where the smallest number was found.
if smallest_number_candidate in self.returned_numbers_collection:
self.returned_numbers_collection.remove(smallest_number_candidate)
else:
self.next_available_positive_integer += 1
return smallest_number_candidate
def addBack(self, number_to_add_back):
# A number can only be added back if it was popped and is not already in the returned list.
if number_to_add_back < self.next_available_positive_integer and number_to_add_back not in self.returned_numbers_collection:
self.returned_numbers_collection.append(number_to_add_back)The core idea is to treat the infinite numbers as a continuous stream and handle any returned numbers separately. We keep track of the next available number in the stream and maintain a special, sorted group for numbers that are added back. To find the smallest, we just pick the smaller of the two: the next in the stream or the smallest from the special group.
Here's how the algorithm would work step-by-step:
class SmallestInfiniteSet:
def __init__(self):
# Tracks the next integer from the infinite sequence that has not yet been popped.
self.next_sequential_integer = 1
# A min-heap stores numbers added back, while a set ensures uniqueness and fast lookups.
self.re_added_numbers_min_heap = []
self.re_added_numbers_set = set()
def popSmallest(self) -> int:
# Decide whether the smallest number is from the re-added pool or the continuous sequence.
if self.re_added_numbers_min_heap and self.re_added_numbers_min_heap[0] < self.next_sequential_integer:
smallest_re_added_number = heapq.heappop(self.re_added_numbers_min_heap)
self.re_added_numbers_set.remove(smallest_re_added_number)
return smallest_re_added_number
# If re-added numbers aren't smaller, take the next integer from the main sequence.
popped_from_sequence = self.next_sequential_integer
self.next_sequential_integer += 1
return popped_from_sequence
def addBack(self, number_to_add: int) -> None:
# Only re-add a number if it's smaller than the sequence counter and not already present.
if number_to_add < self.next_sequential_integer and number_to_add not in self.re_added_numbers_set:
heapq.heappush(self.re_added_numbers_min_heap, number_to_add)
self.re_added_numbers_set.add(number_to_add)| Case | How to Handle |
|---|---|
| Calling `addBack` with a number that has not been previously popped from the set. | The implementation ignores this operation as the number is already considered part of the infinite set of available integers. |
| Calling `addBack(num)` multiple times for the same number before it is popped again. | An auxiliary hash set tracks added-back numbers to prevent duplicate entries in the data structure, making the redundant calls no-ops. |
| A very small number is added back after a long sequence of `popSmallest` calls. | A min-heap ensures the globally smallest number is always returned, correctly prioritizing the small added-back number over the next larger sequential integer. |
| The pool of added-back numbers becomes empty, and `popSmallest` is called. | The solution seamlessly transitions from returning numbers from the added-back pool to drawing from the main continuous sequence of integers. |
| Calling `addBack` on a newly initialized set before any elements are removed. | Since all positive integers are already present, the logic correctly identifies that the number is in the set and the operation has no effect. |
| A number is immediately added back right after it has been popped. | The solution correctly removes the number and then places it into the collection of added-back items, making it available for the next `popSmallest` call. |
| The total number of operations reaches the specified maximum constraint of 1000 calls. | Using a min-heap and hash set ensures all operations have efficient time complexities, scaling well up to the given constraints. |
| The number passed to `addBack` is the smallest possible value, which is 1. | The logic is generalized for any positive integer and correctly handles re-inserting 1, making it the top priority to be popped next. |