題幹與適用場景
給定一個無序陣列 intervals,每個元素是整數時間區間 [start, end)。start 包含在會議中,end 不包含;因此一場會議在時間 t 結束時,另一場會議可以在 t 使用同一房間。假設 0 <= intervals.length <= 100000、0 <= start < end <= 1000000000。回傳安排全部會議所需的最少會議室數量。
例如,[[0, 30], [5, 10], [15, 20]] 回傳 2;[[1, 5], [5, 8]] 回傳 1;空陣列回傳 0。這些限制是本文明確採用的面試契約,不應與某個平台的隱藏限制混用。
公開資料提供了可靠的代表性證據:PracHub 在 2026 年更新了同題題幹;interviewing.io 把最少會議室列為可用時間線或優先佇列解決的區間題;一則 2026 年 2 月的公開面經記錄了雙指標、優先佇列和有限時間範圍陣列三種追問。UMass 的演算法講義則給出區間分組的核心證明:按開始時間處理時,貪心使用的房間數等於最大重疊深度。單篇面經只能證明該候選人的經歷,本文不據此推斷普遍頻率,也不把題目歸屬於某一家公司的固定題庫。
面試官考察點
第一層是建模。題目問的是資源數量,不是合併區間,也不是選擇最多互不衝突會議。最少房間數等於任一時刻同時進行的會議數峰值;這個峰值常稱為區間集合的重疊深度。
第二層是端點語意。半開區間下,結束事件必須在同一時刻的開始事件之前處理。若把 start === end 當成重疊,[1, 5) 與 [5, 8) 會被錯誤算成兩間房。
第三層是不變量與證明。分別排序開始時間和結束時間後,指標不再保留「某個結束屬於哪場會議」的對應關係。候選人需要解釋:只求同時佔用數量時,接下來最早發生的是開始還是結束已經足夠,會議身分並不重要。
最後是需求變化時的資料結構選擇。雙陣列掃描線最直接地回傳數量;若追問具體房間分配、每場會議對應的房間或房間重用紀錄,就需要儲存結束時間與房間編號的最小堆積,不能只留下計數。
回答前需要釐清的問題
- 區間是
[start, end)還是閉區間? 本題採用半開區間,相等端點不衝突。 - 零長度會議是否合法? 本題要求
start < end,不接受[t, t);若業務允許,應先約定它是否佔資源。 - 空輸入回傳什麼? 回傳
0,避免初始化第一場會議造成越界。 - 只回傳數量,還是也要房間分配? 數量可用雙陣列;分配需要保留會議身分和可重用房間。
- 輸入可以修改嗎? 下方實作複製開始、結束時間,不改變呼叫端的陣列。
- 時間是否一定是安全整數? 目前上限在 JavaScript 安全整數範圍內;更大的時間戳需要重新約定表示方式。
- 輸入是否可能無效? 面試題通常保證限制成立。正式系統應在系統邊界驗證,而不是把驗證混入核心演算法。
30 秒回答框架
「我會先確認區間是半開區間,所以同一時刻結束的房間可以立即重用。只求最少數量時,我把開始和結束時間分別排序,用兩個指標按時間掃描;若下一個開始嚴格早於最早結束,就增加佔用,否則先釋放房間,並記錄佔用峰值。
這個峰值既是下界,也是可達到的上界:同時進行的會議必須使用不同房間,而按開始時間處理時,只有所有現有房間都未結束才會開新房。排序決定總時間是 O(n log n),額外空間是 O(n)。如果追問具體房間分配,我會改用儲存結束時間和房間編號的最小堆積。」
分步深入解答
第一步:用兩個有序事件流計算佔用峰值
把所有開始時間升冪放入 starts,所有結束時間升冪放入 ends。startIndex 指向下一個未處理的開始事件,endIndex 指向下一個未處理的結束事件,roomsInUse 表示掃描位置之後仍被佔用的房間數。有效區間滿足 start < end,所以掃描過程不會在沒有活躍會議時先處理結束事件。
若 starts[startIndex] < ends[endIndex],下一事件是開始:必須新增一個佔用,更新峰值。否則,下一事件先按結束處理:釋放一個房間。這裡刻意使用嚴格小於;端點相等時進入釋放分支,隨後再處理開始事件,恰好實現 [start, end)。
export function minimumMeetingRooms(
intervals: ReadonlyArray<readonly [number, number]>,
): number {
if (intervals.length === 0) return 0
const starts = intervals.map(([start]) => start).sort((a, b) => a - b)
const ends = intervals.map(([, end]) => end).sort((a, b) => a - b)
let startIndex = 0
let endIndex = 0
let roomsInUse = 0
let maximumRooms = 0
while (startIndex < intervals.length) {
if (starts[startIndex] < ends[endIndex]) {
roomsInUse += 1
maximumRooms = Math.max(maximumRooms, roomsInUse)
startIndex += 1
} else {
roomsInUse -= 1
endIndex += 1
}
}
return maximumRooms
}手算 [[0, 30], [5, 10], [15, 20]]:開始序列為 0, 5, 15,結束序列為 10, 20, 30。掃描到 0、5 時佔用從 0 增到 2;10 先釋放到 1;15 再增到 2。峰值是 2。
第二步:證明峰值就是最優答案
先證明掃描計數正確。把每個 [start, end) 看成一個 +1 開始事件和一個 -1 結束事件,並按時間排序;同一時刻先處理結束。掃描到任意事件後,累計和恰好等於該時刻之後仍覆蓋時間線的區間數,也就是正在佔用的會議室數。兩個有序陣列的指標合併,等價於按這個規則走訪全部事件。
再證明峰值是最優答案。設最大重疊深度為 d。某個時刻有 d 場會議同時進行,任何安排都至少需要 d 間房,這是下界。另一方面,按開始時間處理會議時,只有目前所有房間都被尚未結束的會議佔用才會開新房;若開到第 k 間,此刻新會議與另外 k - 1 場會議同時存在,因此 k <= d。所以存在只用 d 間房的安排。下界與可行上界相等,最少房間數就是 d,掃描線回傳的正是這個峰值。
建立兩個陣列是 O(n),兩次排序是 O(n log n),合併掃描是 O(n),總時間 O(n log n),額外空間 O(n)。若時間值來自很小且固定的離散範圍,可以用差分計數換取 O(n + U) 時間與 O(U) 空間;當時間上限為十億時,這種最佳化不合適。
第三步:根據輸出要求選擇最小堆積或差分陣列
另一種做法是先按開始時間排序,再用最小堆積儲存各個佔用房間的結束時間。處理新會議前,彈出所有 end <= start 的元素,然後推入新結束時間;堆積大小峰值就是答案。它同樣需要 O(n log n) 時間和 O(n) 最壞空間。
只求數量時,掃描線更短,也直接揭示「結束先於同刻開始」的規則。最小堆積的價值在擴充性:堆積元素可從 end 擴充為 { end, roomId }。另外維護一個按編號排序的空閒房間堆積,就能在會議結束後回收編號,並把每個原始會議索引映射到具體房間。若題目要求編號最小的可用房間,只按結束時間取一間還不夠,必須把「仍佔用」和「已空閒」分開管理。
動態線上預訂是另一道題。未來會議逐筆到達且可能取消時,重新排序全部區間可能太昂貴;需要按業務查詢設計有序事件表、區間樹或日曆索引。不要把離線陣列題的 O(n log n) 答案直接宣稱為線上系統方案。
高品質示範回答
「我先按 [start, end) 解題,所以結束時間等於另一場開始時間時可以重用房間。暴力做法可以逐對檢查衝突,但最壞要 O(n^2);這裡最多有 10 萬場會議,我會排序事件。
我分別得到升冪的開始陣列和結束陣列。兩個指標比較下一次事件:開始時間更早就把目前佔用加一並更新最大值;結束更早或兩者相等就先減一。例如 [[0, 30], [5, 10], [15, 20]] 的佔用變化是 1、2、1、2,所以答案是 2。
正確性有兩部分。掃描的累計值等於目前活躍區間數,因此最大值是最大重疊深度 d。任意安排在 d 場同時發生時至少需要 d 間房;按開始時間的貪心只有在現有房間全被佔用時才增加房間,因此也不會使用超過 d 間。演算法最優。時間是 O(n log n),空間是 O(n)。如果需要回傳每場會議的房間號碼,我會保留原始索引,並用結束時間堆積和空閒房間編號堆積完成分配。」
常見錯誤
- 錯誤表現:把題目寫成合併區間。失敗原因:合併後的區間數量與同時佔用峰值沒有對應關係。修正方法:對開始、結束事件做掃描並記錄活躍數峰值。
- 錯誤表現:端點相等時先處理開始。失敗原因:可立即重用的房間被重複計算。修正方法:在半開區間契約下讓結束事件優先。
- 錯誤表現:使用 JavaScript 預設排序。失敗原因:字典序會令
10排在2前面。修正方法:明確傳入(a, b) => a - b。 - 錯誤表現:只回傳迴圈結束時的
roomsInUse。失敗原因:最後佔用可能低於過程峰值。修正方法:每次增加佔用時維護maximumRooms。 - 錯誤表現:逐對比較所有會議。失敗原因:最壞產生
O(n^2)時間。修正方法:排序後線性合併兩個事件流。 - 錯誤表現:做房間分配時只從堆積彈出一個已結束會議。失敗原因:空閒集合和活躍集合會不完整。修正方法:彈出所有
end <= start的房間,並分開維護可用編號。 - 錯誤表現:未說明區間邊界。失敗原因:相等端點的測試預期會互相矛盾。修正方法:編碼前明確半開或閉區間及同刻事件優先級。
- 錯誤表現:根據公開題庫標籤直接寫公司歸屬。失敗原因:第三方標籤和單一候選人記錄不足以證明固定歸屬。修正方法:證據不足時令
companyName為null,只陳述來源實際支援的範圍。
追問及應對
為什麼兩個排序陣列可以丟掉會議對應關係?
目標只依賴每個時刻的活躍會議數量。下一次計數變化由最早的未處理開始和最早的未處理結束決定,與該結束屬於哪場會議無關。若要回傳分配或單場會議軌跡,對應關係重新變得必要,應改用帶會議索引和房間編號的堆積。
如果區間改成閉區間 [start, end] 呢?
同一時刻結束與開始會衝突。比較條件應讓開始事件先發生,也就是在 start <= end 時增加佔用。更穩妥的說法是明確同刻事件優先級,避免只機械修改運算子。
如何回傳每場會議的房間編號?
保留原始索引並按開始時間排序。用一個最小堆積儲存 { end, roomId } 的佔用房間;處理會議前,把所有 end <= start 的房間編號移入空閒編號最小堆積。若有空閒編號就重用,否則建立新編號,然後記錄 assignment[originalIndex] = roomId。
為什麼最少房間數等於最大重疊數?
最大重疊數給出不可突破的下界,因為同時發生的會議不能共用房間。按開始時間的貪心只有在所有現有房間都仍被佔用時才開房;每次開出的房間數都對應當時同樣多的重疊會議,因此不會超過該下界。兩邊相等即得到最優性。
應該測試哪些邊界?
至少涵蓋空陣列、單一區間、完全不重疊、全部重疊、鏈式端點相等、相同開始時間、相同結束時間、重複區間、輸入逆序,以及接近限制上限的隨機資料。還可用小規模 O(n^2) 或離散事件基準與最佳化實作做隨機差分測試,但一次性驗證程式碼不應混入正式實作。
若時間範圍很小,能否做到線性時間?
可以。用長度與時間域 U 對應的差分陣列,在開始位置加一、結束位置減一,再求前綴和峰值,複雜度為 O(n + U)。只有 U 足夠小且記憶體可控才值得使用;目前十億上限下,排序方案更穩妥。