在面試程式設計師崗位時,我們往往需要經歷一個程式設計面試過程,僱主會藉此考驗面試者的技術實力。然而,這些技術問題有時候卻和我們的實際工作並無太大關係,也由此可能給我們的程式設計面試準備階段帶來很大的壓力。曾在 Facebook 和微軟工作過的 Educative.io 創始人 Fahim ul Haq 近日發文總結了程式設計面試所遇到的問題的 14 種最常見的模式,也許能幫你看清各種程式設計面試問題「背後的真相」。機器之心也在文末補充了我們曾經發布過的另外幾篇有關面試的文章,相信能給即將進入職場的程式設計師帶來幫助。
對很多開發者來說,程式設計工作的面試準備很容易讓人焦慮。面試要涉及的東西實在太多,其中很多還往往與開發者的日常工作無關,只會額外增添壓力。
這種現狀導致了一個後果:現在的開發者往往需要花費數週時間在 LeetCode 等網站上了解綜合數百個問題。與我談過的開發者在面試前的一個常見焦慮問題是:我是否已經解決過足夠多的實際問題?我本可以做到更多嗎?
這就是我想要幫助開發者瞭解每個問題背後的底層模式的原因——這樣他們就不必擔憂解決數百個問題以及被 LeetCode 整得疲憊不堪了。如果你理解面試的通用模式,你就可以將其用作模板,從而解決各種層級的稍有不同的問題。
這裡我將列出最常見的 14 種模式,它們可被用於解決任何程式設計面試問題。另外我還會說明如何識別每種模式,並會為每種模式提供一些問題示例。這些內容都只是蜻蜓點水——我強烈建議你看看課程《Grokking the Coding Interview: Patterns for Coding Questions》,裡面提供了全面的解釋、示例和程式設計實踐。
下面的模式說明假設你已經知悉了資料結構。如果你還不瞭解,可以透過這些課程複習一下資料結構:https://www.educative.io/m/data-structures
我們今天將說明以下 14 種模式:
1.滑動視窗
2.二指標或迭代器
3.快速和慢速指標或迭代器
4.合併區間
5.迴圈排序
6.原地反轉連結串列
7.樹的寬度優先搜尋(Tree BFS)
8.樹的深度優先搜尋(Tree DFS)
9.Two Heaps
10.子集
11.經過修改的二叉搜尋
12. 前 K 個元素
13. K 路合併
14.拓撲排序
我們開始吧!
1.滑動視窗
滑動視窗模式是用於在給定陣列或連結串列的特定視窗大小上執行所需的操作,比如尋找包含所有 1 的最長子陣列。從第一個元素開始滑動視窗並逐個元素地向右滑,並根據你所求解的問題調整視窗的長度。在某些情況下視窗大小會保持恆定,在其它情況下視窗大小會增大或減小。
下面是一些你可以用來確定給定問題可能需要滑動視窗的方法:
問題的輸入是一種線性資料結構,比如連結串列、陣列或字串
你被要求查詢最長/最短的子字串、子陣列或所需的值
你可以使用滑動視窗模式處理的常見問題:
大小為 K 的子陣列的最大和(簡單)
帶有 K 個不同字元的最長子字串(中等)
尋找字元相同但排序不一樣的字串(困難)
2.二指標或迭代器
二指標(Two Pointers)是這樣一種模式:兩個指標以一前一後的模式在資料結構中迭代,直到一個或兩個指標達到某種特定條件。二指標通常在排序陣列或連結串列中搜尋配對時很有用;比如當你必須將一個陣列的每個元素與其它元素做比較時。
二指標是很有用的,因為如果只有一個指標,你必須繼續在陣列中迴圈回來才能找到答案。這種使用單個迭代器進行來回在時間和空間複雜度上都很低效——這個概念被稱為「漸進分析(asymptotic analysis)」。儘管使用 1 個指標進行暴力搜尋或簡單普通的解決方案也有效果,但這會沿 O(n²) 線得到一些東西。在很多情況中,二指標有助於你尋找有更好空間或執行時間複雜度的解決方案。
用於識別使用二指標的時機的方法:
可用於你要處理排序陣列(或連結列表)並需要查詢滿足某些約束的一組元素的問題
陣列中的元素集是配對、三元組甚至子陣列
下面是一些滿足二指標模式的問題:
求一個排序陣列的平方(簡單)
求總和為零的三元組(中等)
比較包含回退(backspace)的字串(中等)
3.快速和慢速指標
快速和慢速指標方法也被稱為 Hare & Tortoise 演算法,該演算法會使用兩個在陣列(或序列/連結串列)中以不同速度移動的指標。該方法在處理迴圈連結串列或陣列時非常有用。
透過以不同的速度進行移動(比如在一個迴圈連結串列中),該演算法證明這兩個指標註定會相遇。只要這兩個指標在同一個迴圈中,快速指標就會追趕上慢速指標。
如何判別使用快速和慢速模式的時機?
處理連結串列或陣列中的迴圈的問題
當你需要知道特定元素的位置或連結串列的總長度時
何時應該優先選擇這種方法,而不是上面提到的二指標方法?
有些情況不適合使用二指標方法,比如在不能反向移動的單連結連結串列中。使用快速和慢速模式的一個案例是當你想要確定一個連結串列是否為迴文(palindrome)時。
下面是一些滿足快速和慢速指標模式的問題:
連結串列迴圈(簡單)
迴文連結串列(中等)
環形陣列中的迴圈(困難)
4.合併區間
合併區間模式是一種處理重疊區間的有效技術。在很多涉及區間的問題中,你既需要找到重疊的區間,也需要在這些區間重疊時合併它們。該模式的工作方式為:
給定兩個區間(a 和 b),這兩個區間有 6 種不同的互相關聯的方式:
理解並識別這六種情況有助於你求解範圍廣泛的問題,從插入區間到最佳化區間合併等。
那麼如何確定何時該使用合併區間模式呢?
如果你被要求得到一個僅含互斥區間的列表
如果你聽到了術語「重疊區間(overlapping intervals)」
合併區間模式的問題:
區間交叉(中等)
最大 CPU 負載(困難)
5. 迴圈排序
這一模式描述了一種有趣的方法,處理的是涉及包含給定範圍內數值的陣列的問題。迴圈排序模式一次會在陣列上迭代一個數值,如果所迭代的當前數值不在正確的索引處,就將其與其正確索引處的數值交換。你可以嘗試替換其正確索引處的數值,但這會帶來 O(n^2) 的複雜度,這不是最優的,因此要用迴圈排序模式。
如何識別這種模式?
涉及數值在給定範圍內的排序陣列的問題
如果問題要求你在一個排序/旋轉的陣列中找到缺失值/重複值/最小值
迴圈排序模式的問題:
找到缺失值(簡單)
找到最小的缺失的正數值(中等)
6.原地反轉連結串列
在很多問題中,你可能會被要求反轉一個連結串列中一組節點之間的連結。通常而言,你需要原地完成這一任務,即使用已有的節點物件且不佔用額外的記憶體。這就是這個模式的用武之地。該模式會從一個指向連結串列頭的變數(current)開始一次反轉一個節點,然後一個變數(previous)將指向已經處理過的前一個節點。以鎖步的方式,在移動到下一個節點之前將其指向前一個節點,可實現對當前節點的反轉。另外,也將更新變數「previous」,使其總是指向已經處理過的前一個節點。
如何識別使用該模式的時機:
如果你被要求在不使用額外記憶體的前提下反轉一個連結串列
原地反轉連結串列模式的問題:
反轉一個子列表(中等)
反轉每個 K 個元素的子列表(中等)
7.樹的寬度優先搜尋(Tree BFS)
該模式基於寬度優先搜尋(BFS)技術,可遍歷一個樹並使用一個佇列來跟蹤一個層級的所有節點,之後再跳轉到下一個層級。任何涉及到以逐層級方式遍歷樹的問題都可以使用這種方法有效解決。
Tree BFS 模式的工作方式是:將根節點推至佇列,然後連續迭代知道佇列為空。在每次迭代中,我們移除佇列頭部的節點並「訪問」該節點。在移除了佇列中的每個節點之後,我們還將其所有子節點插入到佇列中。
如何識別 Tree BFS 模式:
如果你被要求以逐層級方式遍歷(或按層級順序遍歷)一個樹
Tree BFS 模式的問題:
二叉樹層級順序遍歷(簡單)
之字型遍歷(Zigzag Traversal)(中等)
8.樹的深度優先搜尋(Tree DFS)
Tree DFS 是基於深度優先搜尋(DFS)技術來遍歷樹。
你可以使用遞迴(或該迭代方法的技術棧)來在遍歷期間保持對所有之前的(父)節點的跟蹤。
Tree DFS 模式的工作方式是從樹的根部開始,如果這個節點不是一個葉節點,則需要做三件事:
1.決定現在是處理當前的節點(pre-order),或是在處理兩個子節點之間(in-order),還是在處理兩個子節點之後(post-order)
為當前節點的兩個子節點執行兩次遞迴呼叫以處理它們
如何識別 Tree DFS 模式:
如果你被要求用 in-order、pre-order 或 post-order DFS 來遍歷一個樹
如果問題需要搜尋其中節點更接近葉節點的東西
Tree DFS 模式的問題:
路徑數量之和(中等)
一個和的所有路徑(中等)
9. Two Heaps
在很多問題中,我們要將給定的一組元素分為兩部分。為了求解這個問題,我們感興趣的是瞭解一部分的最小元素以及另一部分的最大元素。這一模式是求解這類問題的一種有效方法。該模式要使用兩個堆(heap):一個用於尋找最小元素的 Min Heap 和一個用於尋找最大元素的 Max Heap。該模式的工作方式是:先將前一半的數值儲存到 Max Heap,這是由於你要尋找前一半中的最大數值。然後再將另一半儲存到 Min Heap,因為你要尋找第二半的最小數值。在任何時候,當前數值列表的中間值都可以根據這兩個 heap 的頂部元素計算得到。
識別 Two Heaps 模式的方法:
在優先順序佇列、排程等場景中有用
如果問題說你需要找到一個集合的最小/最大/中間元素
有時候可用於具有二叉樹資料結構的問題
Two Heaps 模式的問題:
查詢一個數值流的中間值(中等)
10. 子集
很多程式設計面試問題都涉及到處理給定元素集合的排列和組合。子集(Subsets)模式描述了一種用於有效處理所有這些問題的寬度優先搜尋(BFS)方法。
該模式看起來是這樣:
給定一個集合 [1, 5, 3]
1. 從一個空集開始:[[]]
2.向所有已有子集新增第一個數 (1),從而創造新的子集:[[], [1]]
3.向所有已有子集新增第二個數 (5):[[], [1], [5], [1,5]]
4.向所有已有子集新增第三個數 (3):[[], [1], [5], [1,5], [3], [1,3], [5,3], [1,5,3]]
下面是這種子集模式的一種視覺表示:
如何識別子集模式:
你需要找到給定集合的組合或排列的問題
子集模式的問題:
帶有重複項的子集(簡單)
透過改變大小寫的字串排列(中等)
11. 經過修改的二叉搜尋
只要給定了排序陣列、連結串列或矩陣,並要求尋找一個特定元素,你可以使用的最佳演算法就是二叉搜尋。這一模式描述了一種用於處理所有涉及二叉搜尋的問題的有效方法。
對於一個升序的集合,該模式看起來是這樣的:
1.首先,找到起點和終點的中間位置。尋找中間位置的一種簡單方法是:middle = (start + end) / 2。但這很有可能造成整數溢位,所以推薦你這樣表示中間位置:middle = start + (end — start) / 2。
2.如果鍵值(key)等於中間索引處的值,那麼返回這個中間位置。
3.如果鍵值不等於中間索引處的值:
4.檢查 key < arr[middle] 是否成立。如果成立,將搜尋約簡到 end = middle — 1 5.檢查 key > arr[middle] 是否成立。如果成立,將搜尋約簡到 end = middle + 1
下面給出了這種經過修改的二叉搜尋模式的視覺表示:
經過修改的二叉搜尋模式的問題:
與順序無關的二叉搜尋(簡單)
在經過排序的無限陣列中搜尋(中等)
12. 前 K 個元素
任何要求我們找到一個給定集合中前面的/最小的/最常出現的 K 的元素的問題都在這一模式的範圍內。
跟蹤 K 個元素的最佳的資料結構是 Heap。這一模式會使用 Heap 來求解多個一次性處理一個給定元素集中 K 個元素的問題。該模式是這樣工作的:
1. 根據問題的不同,將 K 個元素插入到 min-heap 或 max-heap 中
2.迭代處理剩餘的數,如果你找到一個比 heap 中數更大的數,那麼就移除那個數並插入這個更大的數
這裡無需排序演算法,因為 heap 將為你跟蹤這些元素。
如何識別前 K 個元素模式:
如果你被要求尋找一個給定集合中前面的/最小的/最常出現的 K 的元素
如果你被要求對一個數值進行排序以找到一個確定元素
前 K 個元素模式的問題:
前面的 K 個數(簡單)
最常出現的 K 個數(中等)
13. K 路合併
K 路合併能幫助你求解涉及一組經過排序的陣列的問題。
當你被給出了 K 個經過排序的陣列時,你可以使用 Heap 來有效地執行所有陣列的所有元素的排序遍歷。你可以將每個陣列的最小元素推送至 Min Heap 以獲得整體最小值。在獲得了整體最小值後,將來自同一個陣列的下一個元素推送至 heap。然後,重複這一過程以得到所有元素的排序遍歷結果。
該模式看起來像這樣:
1.將每個陣列的第一個元素插入 Min Heap
2.之後,從該 Heap 取出最小(頂部的)元素,將其加入到合併的列表。
3.在從 Heap 移除了最小的元素之後,將同一列表的下一個元素插入該 Heap
4.重複步驟 2 和 3,以排序的順序填充合併的列表
如何識別 K 路合併模式:
具有排序陣列、列表或矩陣的問題
如果問題要求你合併排序的列表,找到一個排序列表中的最小元素
K 路合併模式的問題:
合併 K 個排序的列表(中等)
找到和最大的 K 個配對(困難)
14. 拓撲排序
拓撲排序可用於尋找互相依賴的元素的線性順序。比如,如果事件 B 依賴於事件 A,那麼 A 在拓撲排序時位於 B 之前。
這個模式定義了一種簡單方法來理解執行一組元素的拓撲排序的技術。
該模式看起來是這樣的:
1.初始化。a)使用 HashMap 將圖(graph)儲存到鄰接的列表中;b)為了查詢所有源,使用 HashMap 記錄 in-degree 的數量
2.構建圖並找到所有頂點的 in-degree。a)根據輸入構建圖並填充 in-degree HashMap
3.尋找所有的源。a)所有 in-degree 為 0 的頂點都是源,並會被存入一個佇列
4.排序。a)對於每個源,執行以下操作:i)將其加入到排序的列表;ii)根據圖獲取其所有子節點;iii)將每個子節點的 in-degree 減少 1;iv)如果一個子節點的 in-degree 變為 0,將其加入到源佇列。b)重複 (a),直到源佇列為空。
如何識別拓撲排序模式:
處理無向有環圖的問題
如果你被要求以排序順序更新所有物件
如果你有一類遵循特定順序的物件
拓撲排序模式的問題:
任務排程(中等)
一個樹的最小高度
接下來?
在 LeetCode 上殫精竭慮?學習過這 14 種模式之後,你能對各種問題的解決方案有一個更全面的認知。
如果你有興趣更深入地瞭解以上模式或各個模式下的示例,可以去看看課程《Grokking the Coding Interview: Patterns for Coding Questions》:https://www.educative.io/collection/5668639101419520/5671464854355968。這是 Grokking 面試系列的最新課程,已被兩萬多名學習者用於尋找頂級科技公司的工作崗位。
我能給出的最高推薦語是:我真希望我曾經在準備程式設計面試時就有這個課程。
其它有關面試的文章