Smallest Infinite Set

Medium Heaps

Problem

Implement a data structure that initially contains all positive integers and supports operations to pop the smallest number and add back numbers that were previously popped.

  • 1 ≤ num ≤ 1000
  • At most 1000 calls will be made to popSmallest and addBack.

Example

Input: operations = ["SmallestInfiniteSet", "popSmallest", "popSmallest", "addBack", "popSmallest", "popSmallest"]
Output: [null, 1, 2, null, 2, 3]

Initially, the set contains all positive integers starting from 1. The first popSmallest() returns 1 and removes it. The second popSmallest() returns 2 and removes it. The addBack(2) adds 2 back into the set because it is smaller than the next smallest number (which is 3). The next popSmallest() returns 2 again, removing it. The following popSmallest() returns 3.

Approach

Straightforward Solution

A naive approach would maintain a sorted list of all numbers, but this is impossible due to infinite size. Alternatively, tracking all popped numbers and searching for the smallest missing number each time would be inefficient.

Core Observation

The set conceptually contains an infinite sequence of positive integers starting from 1, but only a finite subset is ever removed or added back. The smallest number to pop is either the smallest number not yet popped (tracked by a counter) or a previously popped number that was added back.

Path to Optimal

The key insight is to maintain a pointer to the next smallest number not yet popped and a min-heap to store numbers that have been added back. When popping, if the heap is non-empty, pop from it (since it contains smaller numbers added back); otherwise, pop the next smallest number from the pointer and increment it. Adding back a number means pushing it into the heap only if it is smaller than the pointer and not already in the heap, ensuring no duplicates.

Optimal Approach

Use a min-heap to store added-back numbers and a set to track membership in the heap. Maintain a pointer 'next_smallest' for the smallest number not yet popped. popSmallest() returns the smallest number from the heap if available; otherwise, returns 'next_smallest' and increments it. addBack(num) pushes 'num' into the heap and set only if 'num' is smaller than 'next_smallest' and not already in the heap. This approach guarantees O(log n) operations and handles the infinite conceptual set efficiently.

Time

O(log n) per operation

Each popSmallest operation either pops from the heap or returns the next smallest number in O(1). Popping from the heap costs O(log n) in the worst case. addBack operations push into the heap and set in O(log n). Since the heap size is at most the number of added-back elements, operations remain efficient.

Space

O(n)

The heap and set store at most all numbers that have been added back, which is bounded by the number of operations, making space usage linear in the number of addBack calls.

Pattern Spotlight

Heaps (Dynamic Minimum Tracking with Auxiliary Set)

When managing a dynamic set with insertions and removals where the smallest element must be retrieved efficiently, use a min-heap combined with a membership set to track and retrieve the minimum in O(log n) time while avoiding duplicates.

Solution

1import heapq
2
3class SmallestInfiniteSet:
4 def __init__(self):
5 self.next_smallest = 1
6 self.heap = []
7 self.in_heap = set()
8
9 def popSmallest(self) -> int:
10 if self.heap:
11 num = heapq.heappop(self.heap)
12 self.in_heap.remove(num)
13 return num
14
15 num = self.next_smallest
16 self.next_smallest += 1
17 return num
18
19 def addBack(self, num: int) -> None:
20 if num < self.next_smallest and num not in self.in_heap:
21 heapq.heappush(self.heap, num)
22 self.in_heap.add(num)

Step-by-Step Solution

1

Initialize the Next Smallest Pointer, Min-Heap, and Membership Set

5self.next_smallest = 1
6self.heap = []
7self.in_heap = set()

Objective

To set up the initial state with the next smallest number to pop, an empty min-heap for added-back numbers, and a set to track heap membership.

Key Insight

The 'next_smallest' pointer represents the smallest number not yet popped or added back, allowing O(1) retrieval when the heap is empty. The min-heap stores numbers added back that are smaller than 'next_smallest', enabling efficient retrieval of the smallest added-back number. The set prevents duplicates in the heap, maintaining correctness and efficiency.

Interview Quick-Check

Core Logic

The 'next_smallest' pointer tracks the smallest number never popped or added back, enabling constant-time retrieval when the heap is empty.

Core Logic

The min-heap stores added-back numbers smaller than 'next_smallest', ensuring the smallest number is always accessible.

State & Boundaries

The set 'in_heap' prevents duplicates in the heap, which is critical for correctness and avoiding redundant work.

2

Pop the Smallest Number by Prioritizing Added-Back Heap Elements

10if self.heap:
11 num = heapq.heappop(self.heap)
12 self.in_heap.remove(num)
13 return num
15num = self.next_smallest
16self.next_smallest += 1
17return num

Objective

To return and remove the smallest number from the data structure, prioritizing numbers in the heap before advancing the next smallest pointer.

Key Insight

If the heap is non-empty, the smallest added-back number must be popped first because it is smaller than the current 'next_smallest'. Removing it from the heap and membership set maintains consistency. If the heap is empty, the 'next_smallest' pointer is returned and incremented, representing the next number in the infinite sequence. This dual-source approach efficiently manages the infinite conceptual set with finite data structures.

Interview Quick-Check

Core Logic

popSmallest returns the smallest number from the heap if available, otherwise returns 'next_smallest' and increments it, ensuring correct ordering.

State & Boundaries

Removing the popped number from 'in_heap' ensures the heap membership set remains accurate.

Common Pitfalls & Bugs

Failing to remove the popped number from the membership set leads to incorrect duplicate prevention in addBack.

3

Add Back a Number Only if It Is Smaller Than Next Smallest and Not Already Present

20if num < self.next_smallest and num not in self.in_heap:
21 heapq.heappush(self.heap, num)
22 self.in_heap.add(num)

Objective

To reinsert a previously popped number into the data structure only if it is smaller than the current 'next_smallest' and not already in the heap.

Key Insight

Numbers greater or equal to 'next_smallest' are implicitly present in the infinite set and do not need to be added back explicitly. Adding only smaller numbers ensures the heap contains only those numbers that were popped and are now candidates to be popped again before 'next_smallest'. Checking membership prevents duplicates, preserving heap integrity and correctness.

Interview Quick-Check

Core Logic

addBack pushes the number into the heap and membership set only if it is smaller than 'next_smallest' and not already present, maintaining correctness.

Common Pitfalls & Bugs

Adding numbers greater or equal to 'next_smallest' would cause duplicates and incorrect behavior.

State & Boundaries

Membership check prevents duplicate entries in the heap, which is essential for correctness.

Line Analysis

This solution has 7 Critical lines interviewers watch for.

Line 11 Critical
num = heapq.heappop(self.heap)

Pop the smallest number from the heap.

Retrieves the smallest added-back number in O(log n) time, ensuring correct ordering of popped numbers.

Line 20 Critical
if num < self.next_smallest and num not in self.in_heap:

Check if the number to add back is smaller than next smallest and not already in the heap.

Ensures only numbers that have been popped and are smaller than the current pointer are added back, preventing duplicates and preserving the infinite set abstraction.

Line 6 Critical
self.heap = []

Initialize an empty min-heap to store numbers added back.

The min-heap efficiently maintains the smallest added-back numbers, allowing O(log n) insertion and removal.

Line 7 Critical
self.in_heap = set()

Initialize a set to track membership of numbers in the heap.

The set prevents duplicates in the heap, which is critical for correctness and efficiency.

Line 12 Critical
self.in_heap.remove(num)

Remove the popped number from the membership set.

Maintains consistency between the heap and membership set, preventing stale entries and ensuring correctness of addBack.

Line 21 Critical
heapq.heappush(self.heap, num)

Push the number into the min-heap.

Adds the number back into the candidate pool for popping, maintaining heap order for efficient retrieval.

Line 22 Critical
self.in_heap.add(num)

Add the number to the membership set.

Tracks the presence of the number in the heap to prevent duplicates and maintain correctness.

Line 5 Core
self.next_smallest = 1

Initialize the pointer to the next smallest number to pop, starting at 1.

This pointer tracks the smallest number not yet popped or added back, enabling efficient retrieval without storing the entire infinite set.

Line 13 Core
return num

Return the popped number from the heap.

Completes the popSmallest operation by returning the smallest available number from the added-back set.

Line 15 Core
num = self.next_smallest

Assign the current next smallest number to be returned.

When the heap is empty, the next smallest number is the pointer value, representing the smallest number never popped or added back.

Line 16 Core
self.next_smallest += 1

Increment the next smallest pointer for future pops.

Advances the pointer to the next number in the infinite sequence, maintaining correctness of the infinite set abstraction.

Line 17 Core
return num

Return the next smallest number from the infinite sequence.

Completes the popSmallest operation when no added-back numbers are available.

Line 10 Core
if self.heap:

Check if the heap contains any added-back numbers.

Determines whether to pop from the heap or return the next smallest number from the infinite sequence.

Test Your Understanding

Why is it necessary to maintain both a min-heap and a set for the added-back numbers?

The min-heap efficiently retrieves the smallest added-back number, but it does not prevent duplicates. The set tracks membership to avoid pushing duplicates into the heap, ensuring correctness and preventing redundant entries.

Related Problems

Heaps pattern

Don't just read it. Drill it.

Reconstruct Smallest Infinite Set from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Smallest Infinite Set drill

or drill a free problem