📘 概念導讀

Problem Solving & Complexity

刷題真正要練的不是「背更多題」,而是兩件可遷移的能力:一套遇到陌生題的拆解流程,以及一雙估得出時間/空間成本的眼睛。前者讓你在白板前不慌、能一步步逼近最佳解;後者讓你在動手前就知道哪個方向值得走。這一章把這兩件事講清楚,後面每個 pattern 章都建立在它之上。

解題七步法

面試時最忌「看到題目直接寫」。先把問題吃透、跑過例子、講清楚暴力解,再開始優化,往往比急著寫更快收斂到正解。

  1. 聽清楚 (Listen):抓住每個條件。「已排序」「只含正整數」「會重複呼叫」這類字眼往往就是解法的鑰匙。
  2. 畫例子 (Draw Example):手造一個夠大、非特例的例子。太小的例子會誤導,特例(空、全相同)留到測試階段。
  3. 先寫暴力解 (Brute Force):講出一個一定對但很慢的解,標出它的複雜度。這是優化的起點,不是恥辱。
  4. 優化 (Optimize):用下面的 BUD 框架找瓶頸,逐步改進。
  5. 走一遍 (Walk Through):把優化後的想法在腦中或紙上完整跑過一次,確認邏輯再落鍵盤。
  6. 實作 (Implement):寫乾淨、模組化的程式。卡住時先用一個命名好的函式佔位,之後再補。
  7. 測試 (Test):先看概念性錯誤,再跑特例、邊界、與大輸入。

TIP

「先暴力、再優化」不是浪費時間。暴力解定義了問題的正確性基準,也常常是優化版的退路;BCR(見下)則告訴你優化還有多少空間。

BUD 最佳化框架

拿到一個可運作但太慢的解後,沿三個方向找突破口:

NOTE

三個方向常彼此呼應:消除重複功通常就是打掉瓶頸的手段,而辨識無用功則需要回頭看題目到底要什麼。

BCR:最佳可能複雜度

BCR(Best Conceivable Runtime) 是「光是讀完必要輸入、產生必要輸出」就無法避免的下界,與你目前的解法無關。

它的用途是喊停:當你的解已達到 BCR,就不必再硬擠優化;若還沒到,BCR 會提示「理論上還有空間」,常見手段是「用空間換時間」或「預處理出有序/索引結構」。

複雜度分析硬知識

概念重點
捨棄常數與非主導項O(2n)O(n + 100) 都是 O(n)O(n^2 + n)O(n^2)
不同輸入別共用 n走訪兩個長度不同的陣列是 O(a + b)O(a * b),不是 O(n)O(n^2)
加法 vs 乘法「做完 A 再做 B」是 O(A + B);「A 的每一步都做一次 B」是 O(A * B)
O(log n) 的來源每一步把問題規模減半(二分搜尋、平衡樹下降)。
遞迴的複雜度多分支遞迴約為 O(branches^depth);費氏數列樸素遞迴是 O(2^n),記憶化後降到 O(n)
遞迴的空間同時存在的呼叫只有一條路徑,空間是 O(depth),不是節點總數。

TIP

攤還分析(amortized):動態陣列在容量滿時加倍擴容,單次擴容是 O(n),但把 1 + 2 + 4 + ... + n ≈ 2n 的總成本均攤到 n 次 push,平均每次仍是 O(1)。Hash Map 的「平均 O(1)」也是同理。

back-of-the-envelope 估算

面試與實務都常要在三十秒內估出「這個做法跑得動嗎」。記住幾個錨點就能心算:

NOTE

估算重點是量級而非精確值。常數因子(快取命中、記憶體連續性)在工程上影響實感,但在判斷「哪個複雜度等級」時可以先忽略。

比較排序的下界

任何「只靠兩兩比較」的排序,最壞情況都不可能快於 O(n log n):n 個元素有 n! 種排列,要靠比較區分它們,至少需要 log2(n!) ≈ n log n 次比較。

這條下界解釋了為什麼 O(n log n) 排序在許多解法裡是「無法再砍的瓶頸」;也提示:若想更快,就得跳出比較模型,改用計數排序、桶排序、基數排序這類非比較式做法(詳見 Binary Search → 排序與搜尋總覽)。

跨倉庫導讀