跳到主要內容

演算法

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

主題 · 雙指標與滑動視窗

雙指標與滑動視窗

兩個索引一起往前走,每一步都排除掉不可能的答案:把原本要兩層迴圈的問題變成走一遍就好。

問題
1/15
  1. 6
    0lo
  2. 8
    1
  3. 10
    2
  4. 11
    3
  5. 13
    4
  6. 17
    5
  7. 18
    6
  8. 24
    7
  9. 28
    8
  10. 33
    9
  11. 38
    10
  12. 41
    11
  13. 46
    12
  14. 52
    13
  15. 56
    14
  16. 61
    15hi
正在相加的兩個已經排除答案
雙指標:看了幾組
0
全部跑完
7
暴力雙層迴圈
51

排好序的數字,目標 57。lo 從最小的開始,hi 從最大的開始。

亮起來的是這一步執行的程式碼
function pairWithSum(a: number[], target: number): [number, number] | null {
let lo = 0, hi = a.length - 1;
while (lo < hi) {
const sum = a[lo] + a[hi];
if (sum === target) return [lo, hi];
if (sum < target) lo++;
else hi--;
}
return null;
}
function longestUnique(text: string): [number, number] {
const s = Array.from(text);
const last = new Map<string, number>();
let left = 0, bestStart = 0, bestLen = 0;
for (let right = 0; right < s.length; right++) {
const seen = last.get(s[right]);
if (seen !== undefined && seen >= left) {
left = seen + 1;
}
last.set(s[right], right);
if (right - left + 1 > bestLen) {
bestStart = left;
bestLen = right - left + 1;
}
}
return [bestStart, bestLen];
}

模型假設與範圍

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

什麼時候用

  • 資料已經排好序,要找一組滿足條件的配對(兩數和、三數和、最接近的一對):兩端往中間夾。
  • 要找「最長/最短的連續一段」滿足某個條件:右邊一直擴張、條件被破壞時左邊收縮。
  • 原地處理陣列(去重、把零移到最後、合併兩個排好序的陣列):一個指標讀、一個指標寫。

和其他主題的關係

延伸閱讀
二分搜尋排序

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

操作平均最差
兩數和(雙指標)
前提是陣列已經排好序
O(n)O(n)
兩數和(暴力)O(n²)O(n²)
最長不重複子字串(滑動視窗)
right 和 left 都只會往前走,各走最多 n 步
O(n)O(n)
最長不重複子字串(暴力)
σ 是字元種類數
O(n·σ)O(n²)

空間:O(1) – O(σ),兩數和只要兩個索引;滑動視窗要記每個字元上次出現的位置

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

數的是:檢查了幾組,或指標移動幾次

Big On = 250n = 500n = 1,000n = 2,000成長倍數:實測(理論)
兩數和:雙指標(找不到)O(n)2494999991,999×8.0 (×8.0)
兩數和:暴力(找不到)O(n²)31,125124,750499,5001,999,000×64 (×64)
最長不重複子字串:滑動視窗O(n)3076131,2382,468×8.0 (×8.0)

和其他做法比

雙指標/滑動視窗暴力雙層迴圈
兩數和:找不到(最差)999499,500
兩數和:找得到255,960
最長不重複:26 種字母1,2177,334
最長不重複:100 種字元1,12414,732

n = 1,000。兩數和看的是「檢查了幾組」,最長子字串看的是「指標移動」對上「暴力法看了幾個字元」。暴力法找最長子字串時,每個起點最多只能走到字母種類數加一就一定會重複,所以它的成本是 n 乘上字元種類數:字元種類越多,差距越大。

真實世界裡的它

  • 合併排序的合併步驟,就是兩個指標各走一個已排序的半邊。
  • 網路監控的「過去 60 秒流量」、限流器的滑動視窗,都是時間軸上的滑動視窗。
  • 搜尋引擎把兩個詞的文件清單取交集,就是兩個指標各走一串排好序的文件編號。

取捨與陷阱

  • 兩數和的夾擠法只在排好序時成立;沒排序要先花 O(n log n) 排序,或改用雜湊表 O(n)。
  • 滑動視窗要求條件是「單調」的:窗口變大只會更難滿足、變小只會更容易。不是這樣的條件(例如和剛好等於 k、但有負數)就不能這樣縮。
  • 邊界最容易錯:lo < hi 還是 lo ≤ hi、窗口長度是 right − left 還是 right − left + 1。

LeetCode 練習