LeetCode 2336: Smallest Number in Infinite Set (Pointer + Min-Heap + Set)
LeetCode 2336DesignHeapToday we solve LeetCode 2336 - Smallest Number in Infinite Set.
Source: https://leetcode.com/problems/smallest-number-in-infinite-set/
English
Problem Summary
Design a data structure with two operations: popSmallest() removes and returns the smallest available positive integer, and addBack(num) puts a previously removed number back.
Key Insight
Use a pointer cur for the untouched infinite tail (starting at 1). For numbers smaller than cur that are added back, keep them in a min-heap. A hash set prevents duplicate heap entries.
Algorithm
- popSmallest(): if heap non-empty, pop heap minimum and remove it from set; otherwise return cur then increment cur.
- addBack(num): only meaningful when num < cur and not already in heap-set, then push into heap and mark in set.
Complexity Analysis
popSmallest(): O(log n) when heap used, otherwise O(1).
addBack(num): O(log n) in valid insertion case.
Extra space: O(n) for heap + set.
Common Pitfalls
- Forgetting duplicate protection in addBack.
- Adding numbers >= cur (already naturally present).
- Forgetting to sync heap and set when popping from heap.
Reference Implementations (Java / Go / C++ / Python / JavaScript)
class SmallestInfiniteSet {
private int cur;
private PriorityQueue<Integer> heap;
private HashSet<Integer> inHeap;
public SmallestInfiniteSet() {
cur = 1;
heap = new PriorityQueue<>();
inHeap = new HashSet<>();
}
public int popSmallest() {
if (!heap.isEmpty()) {
int x = heap.poll();
inHeap.remove(x);
return x;
}
return cur++;
}
public void addBack(int num) {
if (num < cur && !inHeap.contains(num)) {
heap.offer(num);
inHeap.add(num);
}
}
}type IntHeap []int
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(int)) }
func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
type SmallestInfiniteSet struct {
cur int
heap IntHeap
inHeap map[int]bool
}
func Constructor() SmallestInfiniteSet {
h := IntHeap{}
heap.Init(&h)
return SmallestInfiniteSet{cur: 1, heap: h, inHeap: map[int]bool{}}
}
func (s *SmallestInfiniteSet) PopSmallest() int {
if len(s.heap) > 0 {
x := heap.Pop(&s.heap).(int)
delete(s.inHeap, x)
return x
}
x := s.cur
s.cur++
return x
}
func (s *SmallestInfiniteSet) AddBack(num int) {
if num < s.cur && !s.inHeap[num] {
heap.Push(&s.heap, num)
s.inHeap[num] = true
}
}class SmallestInfiniteSet {
private:
int cur;
priority_queue<int, vector<int>, greater<int>> pq;
unordered_set<int> inHeap;
public:
SmallestInfiniteSet() : cur(1) {}
int popSmallest() {
if (!pq.empty()) {
int x = pq.top();
pq.pop();
inHeap.erase(x);
return x;
}
return cur++;
}
void addBack(int num) {
if (num < cur && !inHeap.count(num)) {
pq.push(num);
inHeap.insert(num);
}
}
};import heapq
class SmallestInfiniteSet:
def __init__(self):
self.cur = 1
self.heap = []
self.in_heap = set()
def popSmallest(self) -> int:
if self.heap:
x = heapq.heappop(self.heap)
self.in_heap.remove(x)
return x
x = self.cur
self.cur += 1
return x
def addBack(self, num: int) -> None:
if num < self.cur and num not in self.in_heap:
heapq.heappush(self.heap, num)
self.in_heap.add(num)class MinHeap {
constructor() { this.a = []; }
size() { return this.a.length; }
peek() { return this.a[0]; }
push(x) {
const a = this.a;
a.push(x);
let i = a.length - 1;
while (i > 0) {
const p = (i - 1) >> 1;
if (a[p] <= a[i]) break;
[a[p], a[i]] = [a[i], a[p]];
i = p;
}
}
pop() {
const a = this.a;
const top = a[0];
const last = a.pop();
if (a.length) {
a[0] = last;
let i = 0;
while (true) {
let l = i * 2 + 1, r = l + 1, m = i;
if (l < a.length && a[l] < a[m]) m = l;
if (r < a.length && a[r] < a[m]) m = r;
if (m === i) break;
[a[i], a[m]] = [a[m], a[i]];
i = m;
}
}
return top;
}
}
var SmallestInfiniteSet = function() {
this.cur = 1;
this.heap = new MinHeap();
this.inHeap = new Set();
};
SmallestInfiniteSet.prototype.popSmallest = function() {
if (this.heap.size() > 0) {
const x = this.heap.pop();
this.inHeap.delete(x);
return x;
}
return this.cur++;
};
SmallestInfiniteSet.prototype.addBack = function(num) {
if (num < this.cur && !this.inHeap.has(num)) {
this.heap.push(num);
this.inHeap.add(num);
}
};中文
题目概述
设计一个数据结构,支持两个操作:popSmallest() 删除并返回当前最小正整数,addBack(num) 把之前删除的数字放回集合。
核心思路
用指针 cur 表示“还没被取过的无限尾部最小值”(初始为 1)。对于小于 cur 且被加回的数字,用最小堆维护;再用哈希集合去重,避免重复入堆。
算法步骤
- popSmallest():若堆非空,弹出堆顶并从集合删除;否则返回 cur,然后 cur++。
- addBack(num):只有当 num < cur 且不在堆集合中时,才入堆并标记。
复杂度分析
popSmallest():使用堆时为 O(log n),直接走指针时为 O(1)。
addBack(num):有效入堆时为 O(log n)。
额外空间复杂度:O(n)(堆 + 哈希集合)。
常见陷阱
- addBack 没有去重,导致堆里出现重复值。
- 把 num >= cur 的值也加回(这些值本来就在无限尾部中)。
- 从堆弹出时忘记同步删除集合中的标记。
多语言参考实现(Java / Go / C++ / Python / JavaScript)
class SmallestInfiniteSet {
private int cur;
private PriorityQueue<Integer> heap;
private HashSet<Integer> inHeap;
public SmallestInfiniteSet() {
cur = 1;
heap = new PriorityQueue<>();
inHeap = new HashSet<>();
}
public int popSmallest() {
if (!heap.isEmpty()) {
int x = heap.poll();
inHeap.remove(x);
return x;
}
return cur++;
}
public void addBack(int num) {
if (num < cur && !inHeap.contains(num)) {
heap.offer(num);
inHeap.add(num);
}
}
}type IntHeap []int
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(int)) }
func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
type SmallestInfiniteSet struct {
cur int
heap IntHeap
inHeap map[int]bool
}
func Constructor() SmallestInfiniteSet {
h := IntHeap{}
heap.Init(&h)
return SmallestInfiniteSet{cur: 1, heap: h, inHeap: map[int]bool{}}
}
func (s *SmallestInfiniteSet) PopSmallest() int {
if len(s.heap) > 0 {
x := heap.Pop(&s.heap).(int)
delete(s.inHeap, x)
return x
}
x := s.cur
s.cur++
return x
}
func (s *SmallestInfiniteSet) AddBack(num int) {
if num < s.cur && !s.inHeap[num] {
heap.Push(&s.heap, num)
s.inHeap[num] = true
}
}class SmallestInfiniteSet {
private:
int cur;
priority_queue<int, vector<int>, greater<int>> pq;
unordered_set<int> inHeap;
public:
SmallestInfiniteSet() : cur(1) {}
int popSmallest() {
if (!pq.empty()) {
int x = pq.top();
pq.pop();
inHeap.erase(x);
return x;
}
return cur++;
}
void addBack(int num) {
if (num < cur && !inHeap.count(num)) {
pq.push(num);
inHeap.insert(num);
}
}
};import heapq
class SmallestInfiniteSet:
def __init__(self):
self.cur = 1
self.heap = []
self.in_heap = set()
def popSmallest(self) -> int:
if self.heap:
x = heapq.heappop(self.heap)
self.in_heap.remove(x)
return x
x = self.cur
self.cur += 1
return x
def addBack(self, num: int) -> None:
if num < self.cur and num not in self.in_heap:
heapq.heappush(self.heap, num)
self.in_heap.add(num)class MinHeap {
constructor() { this.a = []; }
size() { return this.a.length; }
peek() { return this.a[0]; }
push(x) {
const a = this.a;
a.push(x);
let i = a.length - 1;
while (i > 0) {
const p = (i - 1) >> 1;
if (a[p] <= a[i]) break;
[a[p], a[i]] = [a[i], a[p]];
i = p;
}
}
pop() {
const a = this.a;
const top = a[0];
const last = a.pop();
if (a.length) {
a[0] = last;
let i = 0;
while (true) {
let l = i * 2 + 1, r = l + 1, m = i;
if (l < a.length && a[l] < a[m]) m = l;
if (r < a.length && a[r] < a[m]) m = r;
if (m === i) break;
[a[i], a[m]] = [a[m], a[i]];
i = m;
}
}
return top;
}
}
var SmallestInfiniteSet = function() {
this.cur = 1;
this.heap = new MinHeap();
this.inHeap = new Set();
};
SmallestInfiniteSet.prototype.popSmallest = function() {
if (this.heap.size() > 0) {
const x = this.heap.pop();
this.inHeap.delete(x);
return x;
}
return this.cur++;
};
SmallestInfiniteSet.prototype.addBack = function(num) {
if (num < this.cur && !this.inHeap.has(num)) {
this.heap.push(num);
this.inHeap.add(num);
}
};
Comments