跳到主要內容

演算法

同一個問題,不同的解題思路

主題 · 掃描線與區間

掃描線與區間

把每個區間的起點和終點當成事件排好,從左掃到右:會議室要幾間、行事曆怎麼合併、哪些時段重疊,都變成一次排序加一次掃描。

8
1/17
0481216202401234567
現在同時
0
最多同時
0
掃描線:排序+事件
58
暴力:每一對
64

把 8 個區間變成 16 個事件:開始 +1、結束 −1,依時間排序(比較了 42 次)。時間相同時結束排前面:[a, b) 和 [b, c) 不算重疊。

綠色:掃描線上正在進行的區間。底部藍色:到目前為止合併出的區塊。

亮起來的是這一步執行的程式碼
function maxOverlap(intervals: [number, number][]): number {
const events: [number, number][] = [];
for (const [s, e] of intervals) {
events.push([s, 1], [e, -1]);
}
// Ends (-1) sort before starts (+1) at the same time.
events.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
let active = 0, best = 0;
for (const [, delta] of events) {
active += delta;
best = Math.max(best, active);
}
return best;
}
function mergeIntervals(intervals: [number, number][]): [number, number][] {
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
const out: [number, number][] = [];
for (const [s, e] of sorted) {
const last = out[out.length - 1];
if (last && s <= last[1]) last[1] = Math.max(last[1], e);
else out.push([s, e]);
}
return out;
}

模型假設與範圍

  • Big O 與實測步數以頁面列出的基本操作為單位;視覺化的快照、畫圖、程式高亮和輸出成本另計,不是執行時間 benchmark。

什麼時候用

  • 問題是關於「時段」:同一時間最多幾個、哪些重疊、合併成幾段。把開始和結束當成事件排序,掃一遍。
  • 需要知道「目前有哪些」而不只是數量時,掃描時用堆積或有序集合維護正在進行的那些。

和其他主題的關係

被這些用到
考題:會議室 II
延伸閱讀
貪婪演算法

時間與空間複雜度(Big O)

操作平均最差
掃描線:最多同時幾個、合併區間
排序佔大頭;掃描本身 O(n)
O(n log n)O(n log n)
暴力:每個起點檢查所有區間O(n²)O(n²)
已排序的輸入O(n)O(n)

空間:O(n),2n 個事件

Big O 實測:n 變大時步數怎麼長

數的是:比較次數(掃描線含排序),同一組隨機區間

Big On = 250n = 500n = 1,000n = 2,000成長倍數:實測(理論)
掃描線O(n log n)4,2069,39820,79445,528×11 (×11)
暴力O(n²)62,500250,0001,000,0004,000,000×64 (×64)

和其他做法比

n = 250n = 500n = 1,000n = 2,000
掃描線(排序比較+事件)4,2069,39820,79445,528
暴力(每個起點數覆蓋它的區間)62,500250,0001,000,0004,000,000

同一組隨機區間,兩種方法求「最多同時幾個」。n 從 250 到 2,000,暴力法的檢查變成 64 倍,掃描線只變成 11 倍。

真實世界裡的它

  • 會議室要幾間(見「考題:會議室 II」)、行事曆合併忙碌時段。
  • 天際線問題:掃描線配合堆積追蹤目前最高的建築。
  • 計算幾何:線段相交、矩形聯集面積,都是掃描線的經典應用。

取捨與陷阱

  • 端點相同時的順序決定答案:區間是 [s, e) 就要先處理結束,否則 10 點結束和 10 點開始的兩場會議會被算成重疊。
  • 合併行事曆時,緊接的區間([1, 3) 和 [3, 5))通常要合成一段,和「有沒有重疊」是不同的問題。

LeetCode 練習