一個經典編程面試題的「隱退」

面試程序員很困難。Jeff Atwood 抱怨找一個會寫代碼的候選人是如此艱難。在技術媒體發佈的那些「最佳」面試題中,不多有能讓我提起興趣的——儘管我很喜歡IKEA的這個面試題。Codility和 Interview Street這樣的創業公司從這個具備挑戰性的課題中看到了機會。與此同時,Diego Basch 呼籲咱們中止逼迫求職者進行白板編程。html

對此我沒有什麼更好的建議。我贊成IQ測試和刁難人的問題很是糟糕。在最好的狀況下,它僅僅能測試候選人的一項素質;在最壞的狀況下,它徹底不能說明候選人是由於曾經遇到過相同的問題,仍是靠着本身的能力找到了解決方法。編程題對於一個一成天的工做內容就是寫代碼的人來講是更好的面試方法,可是傳統的方法,不管是電話仍是面對面交流,都不是最優的測試編程能力的方法。一樣,人們也不是很清楚編程題應該是以怎樣的形式呈現——直接解決問題,仍是僅僅把一個算法翻譯成可執行的代碼?程序員

面對着如此多的挑戰,我想到了一個爲我以及其餘在Endeca、 Google 和LinkedIn工做的同僚們服務了多年的面試題。我帶着沉重的心情解這個題,緣由我會在結尾中解釋。可是首先讓我描述下這個問題,而且解釋爲何它如此有效。面試

1222.strip

問題

我把它叫作「分詞」問題並解釋以下:算法

給定一個輸入的字符串和一個包含各類單詞的字典,用空格將字符串分割成一系列字典中存在的單詞。舉個例子,若是輸入字符串是「applepie」而字典中包含了全部的英文單詞,那麼咱們應該獲得返回值「apple pie」。編程

注意,我故意沒有解釋或者漏掉了一些細節,從而給候選人一個弄清楚問題的機會。 這裏我舉一些候選人可能會問的問題,以及我會如何回答數據結構

問:若是輸入字符串自己是一個單詞怎麼辦?app

答:能夠把它看做一個特殊狀況。機器學習

問:我只用考慮分割成兩個單詞的狀況嗎?數據結構和算法

答:不,可是能夠從這種狀況開始。工具

問:若是輸入字符串沒法被分割成單詞怎麼辦?

答:返回null或者相似的東西。

問:有變位或者拼寫錯誤怎麼辦?

答:只須要嚴格分割成字典中有的單詞。

問:若是有多種分割可能性怎麼辦?

答:只須要返回任何一個正確的答案。

問:我在想將字典用前綴樹,後綴樹,Fibonacci堆實現…

答:你不用實現字典。只須要假設它已經被合理地實現了。

問:字典支持哪些操做?

答:字符串查詢——這就是你所須要的所有

問:字典有多大?

答:假設它遠遠大於輸入字符串,但足夠裝入內存。

 

觀察求職者如何討論這些問題,能幫助你瞭解求職者的溝通技巧和對細節的關注,以及求職者對數據結構和算法的基本理解。

 

簡單解法

題目介紹的足夠多了,咱們接着看看解法。一些求職者從問題的簡易版入手,即只考慮把字符串拆分紅兩個單詞。我把它看做一個「傻瓜」解法,而且指望任何有競爭力的軟件工程師能夠給出任何等價於下面解法的代碼。在個人解答中將使用Java實現。

1
2
3
4
5
6
7
8
9
10
11
12
13
String SegmentString(String input, Set<String> dict) {
       int     len = input.length();
       for     (     int     i =     1     ; i < len; i++) {
         String prefix = input.substring(     0     , i);
         if     (dict.contains(prefix)) {
           String suffix = input.substring(i, len);
           if     (dict.contains(suffix)) {
             return     prefix +     " "     + suffix;
           }
         }
       }
       return     null     ;
}

我面試過沒法給出上面解法的求職者——其中一些甚至經過了Google的技術面。如同Jeff Atwood所說,傻瓜問題是避免面試官在那些根本不會編程的求職者身上浪費時間的好辦法。

 

通用解法

固然,這個問題的精華在於它的通常狀況,即輸入字符串能夠被分割成任意數量的單詞。有不少方法能夠解決這個問題,可是最直接的是遞歸回溯。這是一個典型的創建在上個解法上的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String SegmentString(String input, Set<String> dict) {
       if     (dict.contains(input))     return     input;
       int     len = input.length();
       for     (     int     i =     1     ; i < len; i++) {
         String prefix = input.substring(     0     , i);
         if     (dict.contains(prefix)) {
           String suffix = input.substring(i, len);
           String segSuffix = SegmentString(suffix, dict);
           if     (segSuffix !=     null     ) {
             return     prefix +     " "     + segSuffix;
           }
         }
       }
       return     null     ;
}

許多申請軟件工程師職位的候選人沒法在半小時內獲得至關於上面解法的方法(好比一個用顯式棧實現的算法)。我能夠確定他們頗有競爭力而且很會寫代碼,可是我不會把他們安排到有關信息檢索或者機器學習的職位上,尤爲像那些開發大規模搜索功能的公司。

 

運行時間研究

可是等一下,問題不只於此!當一個求職者走到了上面這一步,我會讓他研究這個算法的最差運行時間,用字符串的長度n表示。我聽過從O(n)到O(n!)的各類各樣的回答。

我一般會提供如下提示:

考慮一個想象中的字典,它只包含」a」,」aa」,」aaa」,…,這樣僅由字母」a」組成的單詞。若是輸入字符串是由一長串」a」和最末尾一個」b」組成會發生什麼?

求職者最好能發現這樣的話遞歸回溯將會尋找每個可能的分割,因而問題就變成了舉出單純分割字符串的全部可能性。我把這個問題做爲練習留給讀者本身思考,答案是時間複雜度O(2^n)。

 

高效解法

若是求職者能走到這一步,我會問他是否能夠作得更好。許多求職者意識到這是一個負載問題,更厲害的那些意識到能夠用動態規劃來完成。這是一個用了存儲的解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Map<String, String> memoized;
 
String SegmentString(String input, Set<String> dict) {
       if     (dict.contains(input))     return     input;
       if     (memoized.containsKey(input) {
         return     memoized.get(input);
       }
       int     len = input.length();
       for     (     int     i =     1     ; i < len; i++) {
         String prefix = input.substring(     0     , i);
         if     (dict.contains(prefix)) {
           String suffix = input.substring(i, len);
           String segSuffix = SegmentString(suffix, dict);
           if     (segSuffix !=     null     ) {
             memoized.put(input, prefix +     " "     + segSuffix);
             return     prefix +     " "     + segSuffix;
}
}
memoized.put(input,     null     );
return     null     ;
}

一樣的求職者須要進行時空分析。關鍵點是SegmentString方法只須要在輸入字符串的後綴中執行,而且只有O(n)數量的後綴。我把這個做爲練習留給讀者,這個解法的時間複雜度爲O(n^2)。

 

爲何我喜歡這個問題

有很是多的理由讓我喜歡它。我說幾點:

  • 這是一個在現實的軟件開發過程當中會遇到的問題。我曾經爲Endeca開發搜索詞重寫,這個問題會在拼寫檢查和同義詞擴充時出現。

  • 它不要求任何特殊的知識——僅僅是字符串、集合、表、遞歸和動態規劃的簡單應用。這些都是本科一二年級的基礎內容。

  • 寫出這些代碼並不容易,45分鐘的時間會很緊湊,不論面試是電話進行或者用Collabedit這樣的工具。

  • 這個問題頗有挑戰性,但不是個故意難倒你的問題。它須要一些有條理的分析以及對基本工具的使用。

  • 候選人在這個問題上的表現不是非對即錯的。最糟糕的候選人甚至不能在45分鐘內想出第一個解法。最好的候選人能在10分鐘內實現帶存儲的解法,這使得你有機會問一些更有趣的問題,好比他們如何處理一個大到難以放在內存中的字典。多數候選人的表如今這兩種之間。

 

「退休」愉快

不幸的是,全部好的東西都有盡頭。我最近發現有人把這個題目放到了Glassdoor上。那裏的解法沒有我這篇講的這麼深刻,而我認爲像這樣好的題目應該體面「善終」。

想出一個好的面試題很難,保守祕密一樣很難。祕訣是保守更少的祕訣。一個理想的面試題應該儘可能不涉及更高端的知識。我和個人同事們正在像這個方向努力。一旦咱們有所進展,我天然會分享更多。

同時,我但願每個經歷過度詞問題的人可以感激它帶給咱們的價值。沒有完美的題目,一樣一我的在一個面試題上的表現沒法說明他在工做中的表現會怎樣。儘管如此,這個題目仍然很棒,我知道不少人會懷念它。


原文連接: The Noisy Channel   翻譯: 伯樂在線 王伯
譯文連接: http://blog.jobbole.com/60798/

相關文章
相關標籤/搜索