考題:會議室 II
給一堆會議時段,最少需要幾間會議室?用最小堆積追蹤最早結束的會議,或用掃描線數同時在開的會議數。
解法
8
題目
給一堆以 [start, end) 表示的會議,回傳最少需要幾間會議室。例如 [[0, 30], [5, 10], [15, 20]] → 2。(LeetCode 253 是 Premium 題;2406 是同一題的免費版本,見下方練習題。)1/15
最小堆積裡的結束時間(由小到大)
先依開始時間排序。堆積裡放每間使用中會議室的結束時間,最上面就是最早空出來的那一間。
| 解法 | 答案 | 比較次數(含排序) | 額外記憶體 | Big O |
|---|---|---|---|---|
| 暴力:每個開始時間數一次 | 3 間 | 64 | 0 | O(n²) |
| 排序+最小堆積 | 3 間 | 30 | 3 格 | O(n log n) |
| 掃描線(開始、結束各排一次) | 3 間 | 43 | 16 格 | O(n log n) |
亮起來的是這一步執行的程式碼
class MinHeap { private a: number[] = []; get size() { return this.a.length; } peek() { return this.a[0]; } push(x: number) { const a = this.a; a.push(x); for (let i = a.length - 1; 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, top = a[0], last = a.pop()!; if (a.length) { a[0] = last; for (let i = 0; ; ) { const l = 2 * i + 1, r = l + 1; let 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[m], a[i]] = [a[i], a[m]]; i = m; } } return top; }} function minRoomsBrute(meetings: [number, number][]): number { let best = 0; for (const [start] of meetings) { let busy = 0; for (const [s, e] of meetings) { if (s <= start && start < e) busy++; } best = Math.max(best, busy); } return best;} function minRoomsHeap(meetings: [number, number][]): number { const sorted = [...meetings].sort((a, b) => a[0] - b[0]); const ends = new MinHeap(); let best = 0; for (const [start, end] of sorted) { if (ends.size && ends.peek() <= start) ends.pop(); ends.push(end); best = Math.max(best, ends.size); } return best;} function minRoomsSweep(meetings: [number, number][]): number { const starts = meetings.map((m) => m[0]).sort((a, b) => a - b); const ends = meetings.map((m) => m[1]).sort((a, b) => a - b); let rooms = 0, best = 0, j = 0; for (const start of starts) { while (j < ends.length && ends[j] <= start) { rooms--; j++; } rooms++; best = Math.max(best, rooms); } return best;}模型假設與範圍
- Big O 與實測步數以頁面列出的基本操作為單位;視覺化的快照、畫圖、程式高亮和輸出成本另計,不是執行時間 benchmark。
什麼時候用
- 面試官想聽到你把問題換個說法:「最少幾間會議室」就是「同一時刻最多幾場會議」。說出這句,堆積和掃描線兩種解法就都出來了。
- 先講排序+最小堆積:它還能告訴你每場會議分到哪一間;再補充掃描線更省、更短。
和其他主題的關係
時間與空間複雜度(Big O)
| 操作 | 平均 | 最差 |
|---|---|---|
| 暴力:每個開始時間數一次 額外空間 O(1) | O(n²) | O(n²) |
| 排序+最小堆積 堆積最多放「答案」那麼多個 | O(n log n) | O(n log n) |
| 掃描線(開始、結束各排一次) 兩個排序陣列,O(n) 空間 | O(n log n) | O(n log n) |
空間:O(n)
Big O 實測:n 變大時步數怎麼長
數的是:比較次數(排序法含排序),同一組隨機會議
| Big O | n = 250 | n = 500 | n = 1,000 | n = 2,000 | 成長倍數:實測(理論) | |
|---|---|---|---|---|---|---|
| 暴力:每個開始時間數一次 | O(n²) | 62,500 | 250,000 | 1,000,000 | 4,000,000 | ×64 (×64) |
| 排序+最小堆積 | O(n log n) | 3,167 | 6,750 | 15,615 | 33,361 | ×11 (×11) |
| 掃描線(開始、結束各排一次) | O(n log n) | 3,857 | 8,696 | 19,427 | 42,841 | ×11 (×11) |
和其他做法比
| n = 250 | n = 500 | n = 1,000 | n = 2,000 | |
|---|---|---|---|---|
| 暴力:每個開始時間數一次 | 62,500 | 250,000 | 1,000,000 | 4,000,000 |
| 排序+最小堆積 | 3,167 | 6,750 | 15,615 | 33,361 |
| 掃描線(開始、結束各排一次) | 3,857 | 8,696 | 19,427 | 42,841 |
同一組隨機會議,三種解法的比較次數(排序法含排序本身)。n 從 250 到 2,000,暴力法變成 64 倍,堆積只變成 11 倍、掃描線 11 倍。
真實世界裡的它
- 變形:會議室 I(能不能全部參加,只要排序後檢查相鄰)、每場會議指定房間(堆積記房號)。
- 同樣的結構:同時最多幾架飛機在跑道上、雲端主機同時要幾台。
取捨與陷阱
- [s, e) 的邊界:一場在 10 點結束、另一場 10 點開始可以共用一間,所以堆積頂端 ≤ 開始時間就能沿用。
- 每場會議只需要從堆積取出最多一次:取出的是最早空出來的那間,夠用就好,不必把所有已結束的都清掉。