Smallest Number in Infinite Set

題目描述

實作 SmallestInfiniteSet 類別:

輸入: ["SmallestInfiniteSet","addBack","popSmallest","popSmallest","popSmallest","addBack","popSmallest","popSmallest","popSmallest"]
      [[],[2],[],[],[],[1],[],[],[]]
輸出: [null,null,1,2,3,null,1,4,5]

解題思路

1

初始化

current = 1(下一個未被碰過的最小值),Min Heap 和 HashSet 為空

2

popSmallest

如果 Heap 有東西且 < current,從 Heap 取;否則回傳 current++

3

addBack

如果 num < current 且不在 Heap 中,加入 Heap 和 HashSet

程式碼

C#

csharp
public class SmallestInfiniteSet {
    private int current;  // 未被碰過的最小值
    private PriorityQueue<int, int> minHeap;
    private HashSet<int> addedBack;
 
    public SmallestInfiniteSet() {
        current = 1;
        minHeap = new PriorityQueue<int, int>();
        addedBack = new HashSet<int>();
    }
 
    public int PopSmallest() {
        // 優先從 Heap 取(被放回來的小號碼)
        if (minHeap.Count > 0 && minHeap.Peek() < current) {
            int val = minHeap.Dequeue();
            addedBack.Remove(val);
            return val;
        }
        return current++; // 否則取 current 並遞增
    }
 
    public void AddBack(int num) {
        // 只有 < current 的數才需要加回(>= current 的本來就在集合中)
        if (num < current && addedBack.Add(num)) {
            minHeap.Enqueue(num, num);
        }
    }
}

TypeScript

typescript
class SmallestInfiniteSet {
    private current: number;
    private addedBack: Set<number>;
    private heap: number[];
 
    constructor() {
        this.current = 1;
        this.addedBack = new Set();
        this.heap = [];
    }
 
    popSmallest(): number {
        if (this.heap.length > 0 && this.heap[0] < this.current) {
            const val = this.heap.shift()!;
            this.addedBack.delete(val);
            this.heap.sort((a, b) => a - b); // 維持排序
            return val;
        }
        return this.current++;
    }
 
    addBack(num: number): void {
        if (num < this.current && !this.addedBack.has(num)) {
            this.addedBack.add(num);
            this.heap.push(num);
            this.heap.sort((a, b) => a - b);
        }
    }
}

Python

python
import heapq
 
class SmallestInfiniteSet:
    def __init__(self):
        self.current = 1  # 未被碰過的最小值
        self.min_heap = []
        self.added_back = set()
 
    def popSmallest(self) -> int:
        # 優先從 Heap 取
        if self.min_heap and self.min_heap[0] < self.current:
            val = heapq.heappop(self.min_heap)
            self.added_back.remove(val)
            return val
        val = self.current
        self.current += 1
        return val
 
    def addBack(self, num: int) -> None:
        # 只有 < current 的數才需要加回
        if num < self.current and num not in self.added_back:
            self.added_back.add(num)
            heapq.heappush(self.min_heap, num)

複雜度分析

| | Time | Space | |--|------|-------| | popSmallest | O(log n) | O(n) | | addBack | O(log n) | O(n) |

n 是被 addBack 加回的元素數量。

圖解

current=1, heap=[]
pop→1, current=2
pop→2, current=3
pop→3, current=4
addBack(1), heap=[1]
pop→1 (from heap), current=4
pop→4, current=5

進階觀點

💡面試追問

為什麼需要 HashSet? 防止同一個數被多次 addBack 到 Heap 中。沒有 HashSet 的話,Heap 可能有重複元素。

為什麼 num >= current 時不需要 addBack? 因為 current 代表「所有 ≥ current 的數都還在集合中」,所以不需要額外操作。

TreeSet 解法:用 TreeSet(有序集合)也可以,addBack 和 popSmallest 都是 O(log n),且天然去重。

變體題:LC 703 Kth Largest Element in a Stream、LC 295 Find Median from Data Stream。

理解測驗

🤔 addBack(5) 時,如果 current = 3,需要把 5 加入 Heap 嗎?

🤔 popSmallest 時,為什麼要先檢查 Heap?

你可能也想看

215. Kth Largest Element in an Array2542. Maximum Subsequence Score

按 ← → 鍵切換課程