(轉載自 http://zh.lucida.me/blog/whiteboard-coding-demystified/)程序員
面試很困難,技術面試更加困難——只用 45 ~ 60 分鐘是很難考察出面試者的水平的。因此 劉未鵬 在他的 怎樣花兩年時間去面試一我的 一文中鼓勵面試者建立 GitHub 帳號,閱讀技術書籍,創建技術影響力,從而提供給面試官真實,明確,可度量的經歷。面試
這種方法對面試者效果很好,但對面試官效果就很通常——面試官要面對大量的面試者,這些面試者之中可能只有不多人擁有技術博客,但這並不表明他們的技術能力不夠強(也許他們對寫做不感興趣);另外一方面,一些人擁有技術博客,但這也不能說明他們的水平就必定會很牛(也許他們在嘴遁呢)。算法
總之,技術博客和 GitHub 帳號是加分項,但技術面試仍然必不可少。因此,問題又回來了,如何進行高效的技術面試?或者說,如何在 45 ~ 60 分鐘內儘量準確的考察出面試者的技術水平?編程
回答這個問題以前,讓咱們先看下技術面試中的常見問題都有什麼:小程序
技術面試中的常見問題
技術面試中的問題大體能夠分爲 5 類:數組
編碼:考察面試者的編碼能力,通常要求面試者在 20 ~ 30 分鐘以內編寫一段需求明確的小程序(例:編寫一個函數劃分一個整形數組,把負數放在左邊,零放在中間,正數放在右邊);
設計:考察面試者的設計/表達能力,通常要求面試者在 30 分鐘左右內給出一個系統的大體設計(例:設計一個相似微博的系統)
項目:考察面試者的設計/表達能力以及其簡歷的真實度(例:描述你作過的 xxx 系統中的難點,以及你是如何克服這些難點)
腦筋急轉彎:考察面試者的『反應/智力』(例:若是你變成螞蟻大小而後被扔進一個攪拌機裏,你將如何脫身?)
查漏:考察面試者對某種技術的熟練度(例:Java 的基本類型有幾種?)
這 5 類問題中,腦筋急轉彎在外企中早已絕跡(由於它沒法斷定面試者的真實能力),查漏類問題由於實際價值不大(畢竟咱們能夠用 Google)在外企中出現率也愈來愈低,剩下的 3 類問題裏,項目類和設計類問題要求面試官擁有同類項目經驗,只有編碼類問題不須要任何前提,因此,幾乎全部的技術面試中都包含編碼類問題。網絡
然而,最令面試者頭痛的也是這些編碼類問題——由於幾乎全部的當面(On-site)技術面試均要求面試者在白板上寫出代碼,而不是在面試者熟悉的 IDE 或是編輯器中寫出。在個人面試經歷裏,不止一個被面試者向我抱怨:『若是能在計算機上編程,我早就把它搞定了!』就連我本身在面試初期也曾懷疑白板代碼的有效性:『爲何不讓面試者在計算機上寫代碼呢?』數據結構
然而在經歷了若干輪被面試與面試以後,我驚奇的發現白板編程居然是一種至關有效的技術考察方式。這也是我寫這篇文章的緣由——我但願經過這篇文章來闡述爲何要進行白板編程(WHY),什麼是合適的白板編程題目(WHAT),以及如何進行白板編程(HOW),從而既幫助面試者更好的準備面試,也幫助面試官更好的進行面試。數據結構和算法
爲何要進行白板編程
不少面試者但願可以在 IDE 中(而不是白板上)編寫代碼,由於:編輯器
主流 IDE 均帶有智能提示,從而大大提高了編碼速度
IDE 能夠保證程序可以編譯經過
能夠經過 IDE 運行/調試代碼,找到程序的 Bug
我認可第 1 點,白板編程要比 IDE 編程慢不少,但這並不能作爲否定白板編程的理由——由於白板編程每每是 API 無關(所以並不須要你去背誦 API)的一小段(通常不超過 30 行)代碼,並且面試官也會容許面試者進行適當的縮寫(好比把Iterable類型縮寫爲Iter),所以它並不能成爲否定白板編程的理由。
至於第 2 點和第 3 點,它們更不能成爲否定白板編程的藉口——若是你使用 IDE 只是爲了在其幫助下寫出能過編譯的代碼,或是爲了調試改 Bug,那麼我不認爲你是一名合格的程序員——我認爲程序員能夠被分爲兩種:
先確認前條件/不變式/終止條件/邊界條件,而後寫出正確的代碼
先編寫代碼,而後經過各類用例/測試/調試對程序進行調整,最後獲得彷佛正確的代碼
我我的保守估計前者開發效率至少是後者的 10 倍,由於前者不須要浪費大量時間在 編碼-調試-編碼 這個極其耗時的循環上。經過白板編程,面試官能夠有效的斷定出面試者屬於前者仍是後者,從而招進合適的人才,並把老油條或是嘴遁者排除在外。
除了斷定面試者的開發效率,白板編程還有助於展現面試者的編程思路,並便於面試者和麪試官進行交流:
白板編程的目標並非要求面試者一會兒寫出天衣無縫的代碼,而是:
讓面試者在解題的過程當中將他/他的思惟過程和編碼習慣展示在面試官面前,以便面試官斷定面試者是否具有清晰的邏輯思惟和良好的編程素養
若是面試者陷入困境或是陷阱,面試官也能夠爲其提供適當的輔助,以避免面試陷入無人發言的尷尬境地
正如前文所述,白板編程是一種頗有效的技術面試方式,但這是創建在有效的編程題目的基礎之上:若是編程題目過難,那麼面試極可能會陷入『大眼瞪小眼』的境地;若是編程題目過於簡單(或者面試者背過題目),那麼面試者無需思考就能夠給出正確答案。這兩種狀況都沒法達到考察面試者思惟過程的目的,從而使得面試官沒法正確評估面試者的能力。
既然編程題目很重要,那麼問題來了,什麼纔是合適(合理)的編程題目呢?
在回答這個問題以前,讓咱們先看看什麼編程題目不合適:
我在求職時發現,技術面試的編程題目每每千篇一概——拿我本身來講,反轉單鏈表被問了 5 次,數字轉字符串被問了 4 次,隨機化數組被問了 3 次,最好笑的是在面試某外企時三個面試官都問我如何反轉單鏈表,以致於我得主動要求更換題目以避免誤會。
無獨有偶,我在求職時同時發現不少面試者都隨身帶一個本子或是打印好的材料,上面寫滿了常見的面試題目,一些面試者甚至會祈禱可以被問到上面的題目。
就這個問題,我和個人同窗以及後來的同事討論過,答案是不少面試官在面試前並不會提早準備面試題,而是從網絡上(例如 July 的算法博客)或 編程之美 之類的面試題集上隨機挑一道題目詢問。若是面試者作出來(或背出來)題目那麼經過,若是面試者作不出來就掛掉。
這種面試方式的問題很是明顯:若是面試者準備充分,那麼這些題目根本沒有區分度——面試者極可能會把答案直接背下來;若是面試者未作準備,他/她極可能被一些須要 aha! moment 的題目困住。總之,若是面試題不能評估面試者水平,那麼問它還有什麼意義呢?
下面是一些問濫的編程問題:
白板編程的目標在於考察面試者的編程基本功,而不是考察面試者使用某種語言/類庫的熟練度。因此白板編程題目應儘量庫函數無關——例如:編寫一個 XML 讀取程序就是不合格的題目,由於面試者沒有必要把 XML 庫中的函數名背下來(否則要 Intellisense 幹甚);而原地消除字符串的重複空白(例:"ab c d e"
=> "ab c d e"
)則是一道合格的題目,由於即使不使用庫函數,合格的面試者也可以在 20 分鐘內完成這道題目。
這類問題相似 被問濫的編程問題,它們的特色在於過於直接,以致於面試者不須要思考就能夠給出答案,從而使得面試官沒法考察面試者的思惟過程。快速排序,深度優先搜索,以及二分搜索都屬於這類題目。
須要注意的是,儘管過於直接的算法題目不適合面試,可是咱們能夠將其進行一點改動,從而使其變成合理的題目,例如穩定劃分和二分搜索計數(給出有序數組中某個元素出現的次數)就不錯,儘管它們實際是快速排序和二分搜索的變種。
同 過於直接的算法問題< 相反,過於複雜的題目 屬於另外一個極端:這些題目每每要求面試者擁有極強的算法背景,儘管算法問題是否過於複雜因人而異(在一些 ACM 編程競賽選手的眼裏可能就沒有複雜的題目 -_-),但我我的認爲若是一道題知足了下面任何一點,那麼它就太複雜,不適合面試(不過若是面試者是 ACM 編程競賽選手,那麼能夠無視此規則):
什麼是腦筋急轉彎?
在一些書(例如 誰是谷歌想要的人才?:破解世界最頂尖公司的面試密碼)和電影的渲染下,Google 和微軟這些外企的面試被搞的無比神祕,以致於不少人覺得外企真的會問諸如『井蓋爲何是圓的』或是『貨車能裝多少高爾夫球』這樣的奇詭問題。而實際上,這些題目因爲沒法考察面試者的技術能力而早已在外企中絕跡。反卻是一些國內公司開始使用腦筋急轉彎 做爲面試題目 -_-#
因此,技術面試題目不該該太難,也不該太簡單,不能是腦筋急轉彎,也不能直接來自網絡。
前三點並不難知足:咱們能夠去 算法導論,編程珠璣,以及 計算機程序設計藝術 這些經典算法書籍中的課後題/練習題挑選合適的題目,也能夠本身創造題目。然而,因爲 careercup 這類網站的存在,沒有什麼題目能夠作到絕對原創——畢竟沒有人能阻止面試者把題目發到網上,因此任何編程題目都逃脫不了被公開的命運。
不過,儘管面試者會把編程題目發到網上,甚至會有一些『好心人』給出答案,但這並不表明面試官不能繼續使用這道題:由於儘管題目被公開,但題目的考察點和延伸問題依然只有面試官才知道。這有點像 公鑰加密,公鑰(面試題)是公開的,但私鑰(解法,考察點,以及延伸問題)只有面試官才知道。這樣即使面試者知道面試題,也不會妨礙面試官考察面試者的技術能力。
接下來,讓咱們看看什麼問題適合白板編程。
良好的編程問題都會有不止一種解法。這樣面試者能夠在短期內給出一個不那麼聰明但可實現的『粗糙』算法,而後經過思考(或面試官提示)逐步獲得更加優化的解法,面試官能夠經過這個過程觀察到面試者的思惟方式,從而對面試者進行更客觀的評估。
以 數組最大子序列和 爲例,它有一個很顯然的 O(n^3) 解法,將 O(n^3) 解法稍加改動能夠獲得 O(n^2) 解法,利用分治思想,能夠獲得 O(n*logn) 解法,除此以外它還有一個 o(n) 解法。(編程珠璣 和 數據結構與算法分析 C語言描述 對這道題均有很是精彩的描述,有興趣的朋友能夠自行閱讀)
良好的編程問題應擁有大量考察點,面試官應對這些考察點爛熟於心,從而給出更加客觀量化的面試結果。這裏能夠參考我以前在 從武俠小說到程序員面試 提到的 to_upper
。
良好的編程問題應擁有延伸問題。延伸問題既能夠應對面試者背題的狀況,也能夠漸進的(Incremental)考察面試者的編程能力,同時還保證了面試的延續性(Continuity)。
以 遍歷二叉樹 爲例:面試官能夠從非遞歸中序遍歷二叉樹開始提問,面試者有可能會很快的寫(或是背)出一個使用棧的解法。這時面試官能夠經過延伸問題來判別面試者是否在背題:使用常量空間中序遍歷 帶有父節點指針的二叉樹,或是找到二叉搜索樹中第 n 小的元素。下面是中序遍歷二叉樹的一些延伸問題:
1
2
3
4
5
6
7
8
9
10
11
|
|--中序遍歷二叉樹
|
|--非遞歸中序遍歷二叉樹
|
|--常量空間,非遞歸遍歷帶父節點的二叉樹
| |
| |--在帶父節點的二叉搜索樹尋找第 N 小的元素
| |
| |--能否進一步優化時間複雜度?
|
|--常量空間,非遞歸遍歷不帶父節點的二叉樹
|
上面的問題不但能夠被正向使用(逐步增強難度),也能夠被逆向使用(逐步下降難度):一樣從非遞歸中序二叉樹遍歷開始提問,若是面試者沒法完成這個問題,那麼面試官能夠下降難度,要求面試者編寫一個遞歸版本的中序遍歷二叉樹。
面試以前,面試官應至少獲得如下信息:
接下來,面試官應根據面試者的簡歷/職位確認對面試者的指望值,而後準備好編程題目(而不是面試時即興選擇題目)。面試官應至少準備 4 道題目(2 道簡單題,2 道難題),以應對各類狀況。
面試時,面試官應清楚的陳述題目,並經過若干組用例數據確認面試者真正的理解題目(以避免面試者花很長時間去作不相關的題目,我在以前的面試就辦過這種挫事 -_-#)
在面試者解題時,面試官應全程保持安靜(或傾聽的狀態),若是面試者犯下特別嚴重的錯誤或是陷入苦思冥想,面試官應給出適當的提示,以幫助面試者走出困境完成題目,若是面試者仍是不能完成題目,那麼面試官應換一道略簡單的題目,要知道面試的目的是發現面試者的長處,而非爲難面試者。(一些國內企業彷佛正好相反)
面試以後,面試官應拍照(或謄寫)面試者寫下的代碼,而後把提問的問題發給 HR 和接下來的面試者(以確保問題不會重複)。接下來,面試官應根據面試者的代碼以及其面試表現,儘快寫出面試反饋(Interview Feedback)發給 HR,以便接下來的招聘流程。
面試以前,面試者應至少作過如下準備:
肯定需求
面試者在白板編程時最重要的任務是理解題目,確認需求——肯定輸入/輸出,肯定數據範圍,肯定時間/空間要求,肯定其它限制。以最多見的排序爲例:
有時面試官不會把題目說的特別清楚,這時就須要面試者本身去確認這些需求,不要認爲這是在浪費時間,不一樣的需求會致使大相徑庭的解法,此外確認需求會留給面試官良好的印象。
白板編程
理解題目確認需求以後,面試者就能夠開始在白板上編寫代碼,下面是一些我本身的白板編程經驗:
白板編程無法複製粘貼,因此後期調整代碼結構很是困難。所以咱們最好在開頭寫出程序的大體結構,從而保證以後不會有大改;
咱們能夠經過註釋的形式給出代碼的前條件/不變式/後條件,以劃分爲例:
1
2
3
4
5
6
7
8
9
|
int* partition(int *begin, int *end, int pivot) {
int *par = begin;
for ( ; begin < end; begin++) {
if (*begin < pivot) {
swap(begin, par++)
}
}
return par;
}
|
就不如
1
2
3
4
5
6
7
8
9
10
11
12
|
int* partition(int *begin, int *end, int pivot) {
// [begin, end) should be a valid range
int *par = begin;
// Invariant: All [0, par) < pivot && All [par, begin) >= pivot
for ( ; begin < end; begin++) {
if (*begin < pivot) {
swap(begin, par++)
}
}
// Now All [0, par) < pivot && All [par, end) >= pivot
return par;
}
|
儘管不變式足以驗證程序的正確性,但適當的使用實例數據會大大加強代碼的可信性,以上面的劃分程序爲例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
Given range [2, 3, 4, 5, 1] and pivot 3
[ 2, 3, 4, 5, 1 ]
^ ^
p,b e
[ 2, 3, 4, 5, 1 ]
^ ^
p,b e
[ 2, 3, 4, 5, 1 ]
^ ^ ^
p b e
[ 2, 3, 4, 5, 1 ]
^ ^ ^
p b e
[ 2, 1, 4, 5, 3 ]
^ ^ ^
p b e
[ 2, 1, 4, 5, 3 ]
^ ^
p b,e
Now we have all [0, p) < 3 and all [p, e) >= 3
|
白板編程並不須要面試者在白板上寫出可以一次經過編譯的代碼。爲了節省時間,面試者能夠在和麪試官溝通的基礎上使用縮寫。例如使用 Iter
替代 Iterable
,使用 BQ
替代 BlockingQueue
。(此法尤爲適合於 Java -_-#)
出於緊張或疏忽,通常面試者在白板編程時會犯下各類小錯誤,例如忘了某個判斷條件或是漏了某條語句,空餘的行寬能夠幫助面試者快速修改代碼,使得白板上的代碼不至於一團糟。
這就延伸出了另外一個問題,若是使用大行寬,那麼白板寫不下怎麼辦?一些面試者聰明的解決了這個問題:他們在面試時會自帶一根細筆跡的水筆,專門用於白板編程。
不會作怎麼辦
相信大多數面試者都碰到過面試題不會作的狀況,這裏說說我本身的對策:
我的不建議面試者在面試以後把題目發到網上,不少公司在面試前都會和麪試者打招呼,有的會簽定 NDA(Non Disclosure Agreement)條款以確保面試者不會泄露面試題目。儘管他們不多真的去查,但若是被查到那絕對是得不償失。
我本身在面試以後會把面試中的編程題目動手寫一遍(除非題目過於簡單不值得),這樣既可以驗證本身寫的代碼,也能夠保證本身不會在同一個地方摔倒兩次。
書籍