程式設計師才看得明白的面試聖經

Nextoffer發表於2016-02-20

當我最初開始參加程式設計面試的時候,我所有最心儀的公司都忽視了我。現在回頭看那個時候,我發現自己當時去參加面試都完全沒做任何準備。雖然已經有許多部落格文章和書籍在講程式設計面試,但現在的我作為面試官,坐在桌子的另一邊,還是能看到許多來參加程式設計面試的人沒做任何準備,或者準備得很糟糕。這也就是為什麼我開始寫這篇指南的原因,剛畢業時的我、第一次參加面試的我一定非常想有這麼一份指南來指引自己。而從現在開始,我自己也會照著這份指南去做。

多年以來,我在好幾家公司工作過,所以我的面試技巧得到了很好的磨鍊,而且我參與面試的過程也教會了我該說什麼、該做哪些準備,以及如何面試。在這篇指南里,你會了解到面試的概況、面試取得成功的六大步驟,以及我在考察資料結構和演算法時所考慮的方面。這篇指南無法確保你找到工作,但它能幫助你盡最大可能給面試官留下一個好印象。

宣告:本文中的觀點完全出自個人視角,與我目前或者以前的僱主沒有關係。

面試過程

本節概述了矽谷公司的面試過程,僅僅是個情況介紹,大家可以跳過去往後看。

除了直接申請面試以外,一般說來,還有兩種途徑來獲得面試的機會:由現在的僱主推薦,或者通過LinkedIn。雖然前者會快一些、更尊敬一些,但後者很可能是大部分應聘者所走的路徑。事實上,每天都有無數的招聘人員趴在LinkedIn上,他們唯一的工作就是尋找和接觸有可能換工作的員工,所以一定要保證自己的資訊是最新的,而且要多交人脈、多請別人來認可自己的技能,並且要把你所具備的技能、做過的個人專案或者對開源軟體所做的貢獻加到個人頁面裡去。

最初的接觸一般是通過電子郵件進行的,然後招聘人員會給你打電話,大概瞭解一下你的技術背景。如果你的技能和他們正在尋找的技能一致,他們就會安排一次電話面試,在電話面試時,你可能就會被要求在一份共享的線上文件裡程式設計。那麼你就會知道,這份文件很可能沒有任何程式碼補全和句法高亮的功能。電話面試會持續半小時到45分鐘,如果你表現不錯,就會被邀請去參加現場面試。現在如果沒有電話面試、或者在電話面試之外,你可能還得去參加一個小的程式設計專案。

現場面試由幾次面試組成,總體會持續45分鐘到一個小時。這些面試會和電話面試非常像,只是問題會更難——不過能親眼見到面試官多少算是有所補償。現場面試數週之後,所有反饋應該都被看過、招聘決定就會做出,招誰不招誰也就定了。如果你沒拿到offer,也要明白麵試是一個隨機的過程,包含運氣的成分,不妨把它看作是一次學習的經歷。可能你還會想起布萊恩·阿克頓(BrianActon)面試Facebook和Twitter不成、後來成為WhatsApp聯合創始人的故事。

理論上講,用哪種程式語言並不重要,但你面試需要用某種特定語言來完成的工作時除外,比如iPhone開發者或者前端開發者。我強烈建議你用正在面試的公司所使用的一種程式語言來程式設計(以及練習面試問題)。

試獲得成功的六個步

程式設計面試的目的,是為了確定你的程式設計水平有多高。一般來說,你將被要求用程式設計來完成一個功能或者方法,但有時候,你會需要編輯一個類的定義,或者設計一系列相關的程式碼模組。在任何一種情況下,你都要有條不紊地解決問題,並遵循以下六個步驟:

1.首先,要確保你理解了面試官的問題。許多問題都是故意措辭模糊或者模稜兩可,這個時候你可以請面試官把問題說清楚,從而確保你真正回答面試官的問題。你的提問同時還有一個好處,就是它能給你自己一些時間,讓你的腦子轉起來。

2.用一到兩個例子來確定問題的限制條件和要求(在現場面試時在白板上完成這個過程,在電話面試時在筆記本上完成)。嘗試用中等規模的例子,以便覆蓋到一些特殊情況。如果你能想到可能相關的表格,就把它畫出來。事實上,把你想到的任何東西都寫下來是會有幫助的,因為它能為你提供一個視覺錨點,從而讓你在走不通時或者思考過程中隨時返回某一個點。

3.把話說清楚,這可能是最重要的一步。要試著讓面試儘可能有更多的互動,面試官不知道你在想什麼,而讓他們參與到你的思考過程裡,會讓她給你一些有用的提示,防止你偏向錯誤的方向。你的目標就是要先和麵試官確證你的答案,然後再去寫程式碼,而且你考慮答案越清晰、越高效,你得到的即時反饋也就越好。

4.通過應用以下技巧來找到答案:回想一下你遇到的類似問題,再想想它們是如何被解決的,嘗試各種不同的演算法(分治演算法、貪心演算法、遞迴、排序,等等),把問題分解成更小的、可處理的小問題(這樣你就能得到相應部分的分數),最後再通覽一遍你列出的資料結構,因為有時候,只要想到了正確的資料結構,就能給出正確的答案。

5.當你向面試官問清楚了問題、並向她解釋了你的答案之後,就可以開始寫程式碼了。要記住,在共享文件裡寫程式碼的時候,你可以複製貼上、寫評論,而且能回過頭來完成骨架演算法和功能。但在白板上寫程式碼就不一樣了,它需要你的頭腦很清醒,而且需要你具備管理白板空間的技能。如果足夠幸運的話,現在當你開始在白板左上角動筆的時候,應該非常明白你要寫些什麼東西,而且你要確保在你寫答案的時候,沒有擋住面試官的視線。花點兒時間把程式碼寫得緊湊而美觀一點兒,因為你的程式碼也會是面試反饋的一部分。在你寫程式碼的時候,要大聲解釋你在寫什麼,這會讓你的面試官更容易地跟上你的思路。

6.最後,用不同的例子和特殊案例驗證一下你的程式碼,並且要一行一行地過。這會展示你的思考過程,讓你檢查出小錯誤,並告訴面試官你的辦法是可行的。如果你想得到額外加分的話,甚至可以把單元測試的程式碼寫下來!最後再和麵試官聊一下你的答案在空間和時間利用方面的複雜性,然後結束整場面試。

中提示出的問題

電話面試值得特別一提,因為這是大多數人失利的地方。之所以會這樣,部分原因在於電話面試是招聘過程中第一道真正的關卡,但也有一部分原因在於,這種形式容易造成溝通的錯誤,而且缺乏視覺化線索,所以電話面試是特別嚴酷的。

電話面試有兩大障礙。第一大障礙是,在電話面試的一開始,雙方都能看到的唯一的東西就是一個空白的共享文件。這會讓面試者傾向於過度補償非語言溝通的缺失,從而著急忙慌地在螢幕上進行溝通。令人遺憾的是,這麼做很少會有好結果。所以當務之急並不是去關注那個正在盯著你的空白文件,而是要首先理解和評估問題(也就是完成上述六個步驟中的前四個),同時通過儘可能地沉浸到面試中來彌補現實存在感的缺失(要記住,電話的另一頭是一位可以很容易就被別的事情[比如檢視郵件]分心的面試官)。

電話面試的第二大障礙,就是要同時在電腦上打字和在電話上聊天的後勤保障問題。你不必一隻手敲程式碼、一隻手打電話,也不必把電話調到揚聲器模式,我建議你用電腦上的Google Hangouts接面試電話(你得有一個GoogleVoice號碼,而且得在面試前測試一下)。你還可以用耳麥或者耳機來進一步降低不好的接收效果、提高溝通質量。

演算法+=程式

如果你正在思考為什麼軟體工程的面試和日常程式設計不一樣,那你可能有興趣讀一下Quora上的這條回答。最根本的原因在於:面試是為了測試你在計算機技術方面的基礎,所以會非常偏重演算法和資料結構,因此你可能需要練習一些面試問題,從而讓自己具備解決面試問題的心態。

從短期來看,你所能做的最好的準備工作就是買一塊白板,並通讀一遍《程式設計師面試金典》(Cracking The Code Interview),裡面都是很好的建議,而且裡面的許多面試問題和答案會幫助你確定問題所在,並匹配好回答模式。請參閱本指南最後列出的常用面試問題。

當然了,長遠來看,我們都會死掉,所以我會把事情搞簡單,說一些你絕對應該複習一下的關鍵概念。

陣列/字串

大部分陣列和字串是可互換的,事實上,你遇到的大部分字串處理的問題,都可以在理解陣列的基礎上得到解決。記住這一點之後,你應該懂得如何遍歷陣列,知道如何訪問、轉換和調換其中的每一個元素,而且要懂得如何對它們進行各種不同的集合運算。和其他演算法相比,二分法檢索(Binary search)可能會更多地成為面試問題的核心內容(如果你曾經碰到過有分類陣列的問題,那麼二分法檢索有可能應該是你答案的一部分),你絕對必須知道如何使用它。

排序

和陣列密切相關的,是排序演算法。你不大可能會被要求重複使用一個排序演算法,但很可能你至少知道排序是如何在O(nlogn)的時間裡完成的就行。不過你應該大概知道歸併排序(merge sort)或者快速排序(quicksort)和基數排序(radix sort)的執行細節。

動態陣列/可增陣列

動態陣列可以按需重新調整自己的大小,同時依然提供分時平攤的持續時間訪問。一種典型的做法是,當在一個全排列陣列中增加一個元素的時候,會形成一個新的、更大的陣列,而舊陣列中的元素也會被複制到新陣列裡。你應該在面試時做到完成一個動態陣列。

如果你拿到一個非陣列類問題,但你在答題中需要用到像陣列結構這樣的陣列,不妨少給自己惹麻煩,直接用動態陣列吧。

雜湊對映/雜湊表/詞典/雜湊集合

雜湊表(Hash tables)是程式設計時的瑞士軍刀,很多不同型別的問題(檢查存在、計算頻率、排序,等等)都能用雜湊表來完美解決。它幾乎肯定會出現在你的面試中,而你應該理解它的原理(雜湊功能的角色、衝突如何解決、什麼時候要調整大小、為什麼)以及如何運用它們。

連結串列

連結串列問題在C和C++的面試中最常見,因為它們是弄清楚應聘者是否理解指標的一種簡單的辦法。不過這個點太初級、太基礎了,所以不管用哪種語言,你都應該知道該如何從零做起應用它們。而且由於大部分連結串列問題不過是與人所周知的遍歷還有刪除和插入相關的問題的變體,所以連結串列問題準備起來很容易,你沒有理由拿不到這部分分數。

許多連結串列問題中都會用到一個小技巧,那就是慢速/快速指標技術。它的簡單版含義如下:使用兩個指標迭代生成一個列表,其中一個指標在另一個指標的前面。快速模式下的指標可能會是一個位於前面的固定數值(它有助於確定列表有無迴圈,或者找到列表中的第k個元素),或者也可能會跳過慢速指標經過的多個結點(打個比方,如果快速指標的速度是慢速指標的兩倍,那麼當它到達列表末尾時,慢速指標將會位於列表的中間)。

請注意,當面試官談到連結串列時,他們常常指的是單連結串列,但你無論如何都應該問清楚。

棧/佇列

棧和佇列一般會是你用來解題的資料結構的一部分。你應該知道如何用連結串列和陣列兩種方式來實現它們。

加練兩道題:利用兩個佇列實現一個棧,以及利用兩個棧來實現一個佇列

樹/二叉樹/二叉搜尋樹(BST)/字典樹/堆

你可能不會每天都見到樹和圖,但你很可能會在面試時遇到它們,所以你要徹底地看一下這些資料結構。

樹最一般的定義,是和其他結點沒有或者有一個以上關係的結點的集合,但在實踐中,當面試官說“”的時候,他們指的是一種叫二叉樹的東西。二叉樹是一種樹的型別,它的每個結點都至多有兩個子樹,一般被稱為左子樹和右子樹。

你不應該把二叉樹二叉搜尋樹混淆起來,後者是一種特殊的二叉樹,它的左子樹結點上的值都比父結點小,而右子樹結點上的值都比父結點大或者相等。二叉搜尋樹的優點是,如果樹的結構相對平衡(向面試官問清楚這個問題),那麼查詢、插入和刪除就可以在O(log n)的時間裡完成。二叉搜尋樹的其他重要屬性,就是你跟著所有的左子樹走,就能得到這個樹上最小的元素,而跟著所有的右子樹走,就能得到這個樹上最大的元素。

請注意,是有辦法讓樹一直保持平衡的,最常用的辦法就是紅黑樹和AVL樹。我不會去弄清楚它具體實現的細節,只要知道有這些資料結構就行。

不過你絕對必須知道遍歷樹(tree traversal)演算法:廣度優先搜尋(breadth-first-search)、深度優先搜尋(depth-first-search),以及中序遍歷後序遍歷前序遍歷之間的差別。

以下是在Java實現中序遍歷的例子,它可以列印出一個樹的所有值(前序遍歷和後序遍歷幾乎和這個一樣):

void inOrderTraversal(Node

root) {
 if (root == null) return;
 inOrderTraversal(root.getLeft());
 // System.out.println(root.getValue());
inOrderTraversal(root.getRight());
}

字典樹(trie,讀“tree”)常常被用在字串問題裡,它是一個n元樹,除了根結點以外的每個結點都代表一個字元或者部分或完整的單詞,而且沿著樹的每一條路徑都代表一個單詞。實際上它真的沒有聽起來那麼複雜,只要讀一下維基百科上的頁面、瞭解該如何構建一個字典樹以及如何查詢其中的數值就行。請注意,你可以通過前序遍歷輸出字典樹中的所有鍵。作為一個練習,你可以想一想自己會如何利用字典樹實現自動完成功能。

最後是堆(heaps),它也被稱為優先佇列,是你應該瞭解的最後一種資料結構。它們通常都是滿足堆屬性的二叉樹:每個結點的子樹的值都比結點本身的值小,或者與它相等。所以根結點的值總是最大的,也就是說你總能找到最大值,但代價就是尋找其他任何一個值所需的時間都是O(n)。插入和刪除所需的時間依然是O(logn)

有向圖/無向圖/加權圖

和樹一樣,圖也是由帶子集的結點組成的,但和樹不一樣的地方在於,這些結點可以有多個父結點,所以可能會形成自環(loop)或者(cycle)。除了連結——也被稱作邊(edges)——之外,兩個結點之間可能地有比指標更多的資訊,而且可能會有值和權重。邊有方向的圖被稱為有向圖,而只有雙向指標的圖被稱為無向圖。邊上有權重的圖被稱為加權圖。

有三種方法來表示圖,但你只要搞清楚鄰接矩陣(adjacency matrices)和鄰接表(adjacency lists)就行了。你應該瞭解它們計算的複雜程度、它們需要折衷的地方,以及如何在現實的程式碼中實現它們。用哪種方法取決於你有的圖的型別,比如連線完整的簡單圖可能用鄰接矩陣來實現更好,而稀疏一些的圖則可能用鄰接表來表示更好。

請注意,如果你是在實現加權圖,很可能需要定義一個Edge類。

圖論是一個非常寬泛的話題,所以很難知道一個人應該為一場面試去熟悉多少種圖論演算法,所以我只是列出了我認為可以覆蓋90%圖論問題的內容:你絕對必須知道該如何遍歷一個圖(深度優先或者廣度優先),以及如何做拓撲排序(topological sorting),你應該知道如何實現迪傑斯特拉(Dijkstra)的最短路徑演算法(這裡有一個製作精巧的視訊解釋了這一演算法),同時也要知道如何實現普里姆(Prims)演算法。最後,如果你還知道如何實現A*搜尋演算法(A*searchalgorithm),那就更好了。

其他資料結構

使用以上資料結構,你就可能解決絕大多數問題了,但也請儘管在這個部分下留言,為其他讀者推薦其他資料結構。

位操作

要想處理位元,你必須先得知道在二進位制補碼(two’s complement)標記內部,數字是如何表示的——二進位制補碼和無格式二進位制標記是一樣的,只是負數要“進行位元翻轉之後再加1”。比如要想得到數字-1,你要從用8位二進位制整數表示是00000001的1開始。對每一個位元進行翻轉之後的結果是11111110,再加上1就是11111111,也就成了二進位制補碼中的-1。

左移位運算子“<<”會把位元移向左邊,用0來補上移走之後的空位。

右移位運算子“>>”會把一個位模式向右移,但當向右移動負數時,它的作用在不同程式語言中也不一樣,在Java中,右移位會用符號擴充的辦法,用1來填充負數中的空位。

邏輯右移位運算子“>>>”是Java和Javascript中獨有的,無論數值是多少,它都用0來填充空位。

設定某一位:可以用按位或運算子(|)。

num |= 1 << x; //這行程式碼將會設定位元x

清除某一位:可以用按位與運算子(&),並且用取反運算子(~)來遮蔽所有你不想清除的位元。

num &= ~(1 << x); //這會清除位元x

清除一直到i的所有有效位元

num &= (1 << (i + 1)) -1;

切換某一位元:可以用按位異或運算子(^)

num ^= 1 << x; //這會切換位元x

獲得一個位元:對你想檢查的位元用按位與

bit = num & (1 << x);

設計模式/物件導向程式設計

和麵向物件程式設計相關的問題,一般會涉及到設計相關類裡的集,以便檢驗你對物件導向程式設計的熟悉程度,並瞭解你是如何架構程式碼的。你可以使用介面和/或抽象的類來說明,並記住用單例模式(Singleton)、工廠方法模式(Factory)和策略模式(Strategy)來解決這類問題,在編寫優雅而可維護的程式碼方面,它們能對你有長久的助益。

程式設計應該知道的事情

要知道如何用你正在使用的程式語言來讀取寫入檔案,並且要知道如何生成隨機數

數學

你並不是在面試數學相關的職位,但考慮到我們被計數和測量的問題所包圍,所以有一些數學概念也成了程式設計面試時關注的東西,比較重要的有質數、進位制轉換(base conversions)和一些基本的組合數學。

對於質數,要大概知道為什麼它們很重要,並且要知道每一個數都可以被分解成質數的和。你還得知道如何實現埃拉託斯特尼篩法(sieve of Eratosthenes)。

對於基本的組合數學,你得知道排列和組合。

排列是對一個集合中的數按照一定的次序或者順序進行整理。比如對於集合{1,2,3},就有6種排列的方式,也就是(1,2,3)、(1,3,2)、(2,1,3)、(2,3,1)、(3,1,2)和(3,2,1)。n個不同數字的排列方式一共有n!種。

還有一種排列叫部分排列,也就是從n個數字的集合中取出k個不同的元素,然後再進行排序。這種排列可以用下面的公式來表達:

部分排列公式

組合則是從一個組裡選擇成員的一種方法,因此選擇的順序並不重要。比如一手牌可以被描述成是從52張一摞的牌堆(n=52)中選出5張組成一組(k=5)。從有n個元素的集合中挑出k個元素,當k>n時,不存在相應的組合,否則這k個元素的組合的數量可以用下面的公式來表達:

當k<=n時,從有n個元素的集合中挑出k個元素的組合形式數量的一般公式。

併發

併發問題在面試中並不常見,但也確實有過,所以你肯定不想到時候毫無準備,那就再去看一下如何生成執行緒、使用同步以及鎖定對共享資源的訪問,並理解會導致死鎖(deadlocks)的幾種情況。準備這個話題有一個好辦法,那就是去做出來一個你最喜歡的資料結構的同步版本。

試時的行為舉

做些功課,瞭解一下要面試的公司,瞭解一下你自己,以及為什麼你要去這家公司。要理解公司在做的事、你的新工作涉及哪些東西,以及它最讓你激動的地方是什麼。換工作是件大事,所以要認真對待它,提前做些研究。

保持積極心態。保持一個好的情緒,要微笑,不要談論和你現在或者之前的工作有關的負面資訊,當描述挑戰的時候,要保持樂觀的語調,並強調你從中學到的積極的東西。

本條是前一條裡說的不要向面試官傳遞負面資訊的必然結果。一些面試官會問你現在感覺如何,千萬別說你之前受不了某一位或兩位面試官,一定要說所有事情都非常好。

要保持激情!要讓你的激動之情閃亮全場,並展示出你對軟體開發、技術和解決重大問題的熱情。

要問問題。要真正對你的面試官每天都在做什麼抱有真正的興趣,問問他們工作中遇到的機遇與挑戰,提前準備幾個程式化的問題,顯示一下你對公司和這個職位的興趣。不過無論你做什麼,都別問對方“你感覺如何”。首先,你很可能會收到同樣程式化的回答,其次,把面試你的人擺在那樣一個位置上,也不是什麼好主意。

保持親切感,並形成閉環。當你結束面試之後,給招聘你的人發一句簡短的感謝語,讓他們知道你對這次面試的感覺。

回想你學到的東西。無論結果如何,你都能學到一些東西——可以是知識上的某個缺失,也可以是新的面試問題——所以要做自我反思,從自己的經歷中學習。

祝各位職場和麵試好運!

相關文章