面試準備#

技術面試不只是考察演算法能力,更是觀察你的思維方式與溝通能力。本章整理面試準備的核心策略。


切題四件套#

當面試官提出問題時,請遵循以下四個步驟,將其內化為條件反射:

flowchart LR
    A[1. Clarification<br/>釐清需求] --> B[2. Possible Solutions<br/>探討解法]
    B --> C[3. Coding<br/>撰寫程式碼]
    C --> D[4. Testing<br/>測試驗證]

    style A fill:#e1f5fe
    style B fill:#fff3e0
    style C fill:#e8f5e9
    style D fill:#fce4ec

Step 1: Clarification(釐清需求)#

不要急著解題。首先與面試官溝通,確認題目細節:

  • 詢問邊界條件(Edge cases)
  • 確認輸入資料的規模與限制
  • 探討可能的極端錯誤情況

Step 2: Possible Solutions(探討解法)#

千萬不要一想到某個解法就立刻開始寫程式碼。

  • 全面思考:腦力激盪出所有可能的解法
  • 比較分析:評估不同解法的時間與空間複雜度
  • 選擇最佳:通常選擇時間最快、記憶體最省、或程式碼最優雅的解法
  • 溝通確認:動手前先與面試官確認解法是否符合預期

Step 3: Coding(撰寫程式碼)#

  • 採用標準化、模組化的寫法
  • 應用平時練習累積的程式碼模板
  • 保持程式碼的整潔與可讀性

Step 4: Testing(測試驗證)#

寫完程式碼後,主動進行測試:

  1. 正常資料:一般情況的測試
  2. 邊界資料:極端情況(空值、最大/最小值)
  3. 特殊資料:可能導致錯誤的陰險測資

白板解題七步流程#

上面的「切題四件套」是骨架。在白板或共享編輯器上實戰時,把它展開成更細的七個步驟,能讓你不漏掉關鍵動作、也讓面試官看見你完整的思考軌跡。

步驟動作重點與常見陷阱
1. Listen(聽清楚)聽完整題目,記下每一個獨特條件像「陣列已排序」「同一個函式會被重複呼叫」這類字眼往往是提示:暗示你可以用二分搜尋,或該預先計算/快取結果
2. Draw Example(畫例子)手畫一個夠大、非特例的具體例子別用 [1, 2] 這種太小或太規律的例子,容易誤導你寫出只對特例成立的解
3. Brute Force(暴力解)先講出最直白的暴力解,並給出它的複雜度不要因為它慢就跳過。先有一個能跑的解,面試官會知道你至少能解,後面再優化
4. Optimize(優化)用 BUD 等方法系統性地找優化空間對著暴力解逐項問:瓶頸在哪?哪些是多餘的工作?哪些重算了?
5. Walk Through(走通)動手前,先在腦中或口頭把演算法走一遍急著下筆是大忌。先把邏輯講通,寫的時候才不會邊寫邊改、自亂陣腳
6. Implement(實作)寫整潔、模組化的程式碼命名清楚、把可重用的部分抽成小函式,留給後面測試與除錯的空間
7. Test(測試)自己主動測:正常、邊界、特殊測資從小例子手動代入;發現 bug 時,要有條理地定位,而不是亂改

七步之間是可以來回的。例如在第 7 步測出錯,回到第 5 步重新走通邏輯,是完全正常的;面試官看重的是這套有紀律的流程,而不是一次到位。


BUD 優化法#

當你有了暴力解、要往第 4 步(Optimize)推進時,BUD 提供三個明確的著力點,讓「優化」不再靠運氣。逐一檢視這三點,往往就能找到下一個更快的解。

著力點自問具體例子
Bottlenecks(瓶頸)整個流程最耗時的那一步是什麼?在迴圈裡反覆做線性搜尋(O(n)),改用雜湊表把查找降到 O(1)
Unnecessary work(多餘的工作)有沒有根本不必做的步驟?要找「a + b + c + d = 0」的組合時,固定前三個後第四個可直接算出,不必再開一層迴圈枚舉
Duplicated work(重複的工作)同一份計算是不是被算了很多次?把會重複用到的中間結果預先存進陣列或快取(這正是動態規劃的核心動機)

找到瓶頸後,要集中火力消掉那一個瓶頸——把一個 O(1) 的步驟優化成更快沒有意義,真正影響整體複雜度的是最慢的那一段。


五大解題技巧#

當暴力解都想不出來、或想不到怎麼優化時,下面五種思路可以輪流嘗試,幫你打破僵局:

  1. BUD:對著現有解,逐項檢查瓶頸、多餘的工作、重複的工作(見上一節)。
  2. DIY(Do It Yourself):拿一個具體例子,用直覺手動把答案解出來,再回頭反向工程——觀察「我剛剛腦中是怎麼做到的」,把那個過程翻譯成演算法。
  3. Simplify & Generalize(先簡化再推廣):先把問題的某個限制拿掉、解出簡化版,再一步步把限制加回去、推廣到原題。
  4. Base Case and Build(從基礎往上建):先解最小的情況(n = 0, 1, 2),再看 n 的解能否由 n−1 的解推得。這條路常常自然導向遞迴或動態規劃
  5. Data Structure Brainstorm(資料結構腦力激盪):把常見資料結構(陣列、雜湊表、堆疊、佇列、堆積、樹、圖、Trie……)一個個套上去想,常會突然冒出靈感——「如果用堆積維護當前最大值會怎樣?」

BCR:最佳可能執行時間#

BCR(Best Conceivable Runtime,最佳可能執行時間)指的是:這道題在理論上最快能多快?它其實就是問題的下界——不管演算法多聰明,至少要做的工作量。

BCR 是優化時的指南針:

  • 如果你目前的解已經達到 BCR,就可以收手,不必再鑽牛角尖。
  • 如果還沒達到,BCR 告訴你「還有空間」,並暗示該消掉哪個瓶頸(往往是讓你超過 BCR 下界的那一步)。

例如:一道題若必須讀過所有 n 筆輸入,BCR 就是 O(n);那麼任何 O(n log n) 的解都還有被壓到 O(n) 的可能。

先問下界,是高手的習慣。 動手優化前先估出 BCR,你才知道目標在哪、什麼時候該停。這套「先找下界」的思維,在 複雜度的下界演算法思維總綱 有更完整的展開。


複雜度溝通#

整個流程中,每講一個解法,都要主動附上它的時間與空間複雜度——這是面試官判斷你功底的最直接訊號,不要等他來問。

組合多個步驟的複雜度時,分清楚兩種情況:

情境規則例子
做完 A,做 B(先後)相加 O(A + B)先排序 O(n log n),再掃一遍 O(n),合計 O(n log n)
每個 A 都要做一次 B(巢狀)相乘 O(A × B)對每個元素都做一次線性搜尋,O(n) × O(n) = O(n²)

講複雜度時順手點出「這裡是兩段先後所以相加」「這裡是巢狀所以相乘」,既能避免自己算錯,也讓面試官看見你對複雜度的掌握是清楚而非死背。


刻意練習策略#

走出舒適區#

當你發現做某類題目感到非常順手時,必須強迫自己進入不熟悉的領域。

許多人會陷入「為了切題而切題」的誤區,只反覆練習熟悉的題目。這能帶來短期滿足感,卻無法提升實力。

例如:不熟位元運算,就專門練習位元運算。這過程初期會痛苦,但這正是大腦「長肌肉」的階段。

重複練習#

做過的題目必須「回鍋」練習:

  • 建議頻率:面試前,典型題目至少練習 3 遍
  • 善用 LeetCode:利用 Top 100 Liked 或 Frequently Asked 篩選功能
  • 專項突破:針對弱點進行標籤篩選(Tag Filtering)

題目做出來並不是結束。務必去 LeetCode 的 Discuss 查看高票答案,比較別人的程式碼與自己的差異,思考最佳化思路。


開發環境設定#

鍵盤設定#

調整系統的鍵盤回應速度能顯著提升 Coding 效率:

  • Key Repeat(按鍵重複率):拉到最高
  • Delay Until Repeat(重複前延遲):拉到最短

編輯器選擇#

  1. IDE(推薦):熟練一款,如 PyCharm 或 IntelliJ IDEA
  2. 輕量編輯器:VS Code(強烈推薦)、Sublime Text
  3. Vim:必備技能,遠端伺服器 Debug 時常只有 Vim 可用

心態建設#

錯誤心態:將面試官視為「監考老師」,將面試視為「考試」

正確心態:將面試官視為未來的同事

面試本質上是 Teamwork 的過程。你們正在共同探討技術問題,目標是達成共識並解決它。面試官的存在是為了觀察你的溝通模式,並在必要時輔助你。保持開放、合作的心態,能顯著提升錄取機率。


語法要點#

就地交換#

Python 有 x, y = y, x 這種同時求值的多重賦值,能不借助臨時變數就交換兩數。Kotlin 沒有這個語法糖,但可以用 also 達到同樣效果:

// Kotlin:用 also 就地交換,also 先求出右側再賦值
var x = 1
var y = 2
x = y.also { y = x }

反轉鏈結串列時,Python 可以一行寫成 prev, curr.next, curr = curr, prev, curr.next;Kotlin 沒有多重賦值,改用一個臨時變數把「下一個節點」先接住即可:

// 反轉鏈結串列的經典寫法(需臨時變數暫存 next)
val nextTemp = curr.next
curr.next = prev
prev = curr
curr = nextTemp

關鍵在賦值的求值時機。Python 的 x, y = y, x 會先算完右側才一次賦值;而 Kotlin(如 Java/C++)的 x = y; y = x; 是逐句執行,第一句就把 x 覆蓋掉了,因此必須靠 also 或臨時變數保住原值。

Dummy Node(哨兵節點)#

處理鏈結串列頭部操作時,建議使用 Dummy Node:

val dummy = ListNode()  // 啞節點作為起點
// ... 操作 ...
return dummy.next       // 實際結果

這種寫法在邏輯與程式碼可讀性上都更為清晰。