本文翻譯自Google工程師/面試官Alex Golec的文章:Google Interview Questions Deconstructed: The Knight’s Dialer;來源:實驗樓,翻譯:實驗樓掃地阿姨java
做爲一名Google的工程師和麪試官,今天是我第二次發文分享科技公司面試建議了。這裏先聲明:本文僅表明我我的的觀察、意見和建議。請勿看成來自Google或Alphabet的官方建議或聲明。算法
下面這個問題,是我面試生涯中第一個問題;也是第一個被泄漏出來,以及第一個被禁掉的問題。我喜歡這個問題,由於它有如下優勢:編程
若是你是學生,或者求職者,我但願你經過本文可以瞭解到,面試問題通常會是怎麼樣的。若是你也是面試官,我很樂意分享本身在面試中的風格和想法,如何更好地傳達信息、徵求意見。後端
注意,我將使用Python寫代碼;我喜歡Python由於它易學,簡潔,並且有海量的標準庫。我遇到的不少面試者也很喜歡,儘管咱們推行「不限定語言」的政策,我面試90%的人都用Python。並且,我用的Python 3由於,拜託,這都2018年了。緩存
問題 數據結構
把你的手機撥號頁想象成一個棋盤。棋子走只能走「L」形狀,橫着兩步,豎着一步;或者豎着兩步,橫着一步。多線程
如今,假設你撥號只能像棋子同樣走「L」形狀。每走完一個「L」形撥一次號,起始位置也算撥號一次。問題:從某點開始,在N步內,你能夠撥到多少不一樣的數字?架構
討論函數
每次面試,我基本都會分紅兩個部分:首先咱們找出算法方案,而後讓面試者在代碼中實現。我說「咱們找出算法方案」,由於這個過程我不是沉默的獨裁者。在這樣高壓下,設計並實現一種算法,45分鐘時間並不算充足。
我一般會讓面試者主導討論,讓他們去產生想法,我嘛,就在旁邊,時不時地泄漏一點點「天機」。面試者們能力越強,我須要泄漏的「天機」就越少;可是目前爲止,我還沒遇到一點都不須要我提示的面試者。
有一點我想強調一下,重要的很:做爲面試官,個人職責可不是坐那看着你們失敗搞砸。我想要給你們正面的反饋,給你們機會去展示你們最擅長的點。給他們提示,就像是在說:吶,這一步路我給你鋪上,但這只是爲了讓你展現給我,你在後面的路上能走的更遠。《面試千萬不要犯這 5 點低級錯誤!》你必定要看一下。
當聽完面試官的問題,你應該作什麼?切記不要馬上就去寫代碼,而是在黑板上試着一步一步去分解問題。分解問題可以幫助你尋找到規律,特例等等,逐漸在大腦中造成解決方案。好比,你如今從數字6開始走,能走2步,會有以下組合:
6–1–8
6–1–6
6–7–2
6–7–6
6–0–4
6–0–6
一共有6種組合。你能夠試着用鉛筆在紙上畫,相信我,有時候動手去解決問題會發生意想不到的事,比你盯着在腦殼裏想更神奇。
怎麼樣?你腦海裏有方案了嗎?
第0階:到達下一步
使用這個問題面試,最讓我驚訝的是,太多人都卡在了計算從某個特定點跳出時,一共有多少種可能,即鄰Neighbors。個人建議是:當你不肯定時,先寫個佔位符,而後請求面試官可否晚點實現這一部分。
這個問題的複雜性並不在Neighbors的計算;我在乎的是你如何計算出總數。全部花費在計算Neighbors上的時間其實都是浪費。
我會接受「讓咱們假設有一個函數能給出我Neighbors」。固然,我也可能會讓你後面有時間再去實現這一步,你只須要這樣寫,而後繼續。
並且,若是一個問題的複雜性不在這裏,你也能夠問我能不能先略過,通常我都是容許的。我卻是不介意麪試者不知道問題的複雜性在哪裏,尤爲剛開始他們尚未全面瞭解問題的時候。
至於Neighbors函數,由於數字永遠不變,你能夠直接寫一個Map而後返回符合的值。
第1階:遞歸
聰明的你可能注意到了,這個問題能夠經過枚舉出全部符合條件的數字,而後計算。這裏可使用遞歸產生這些值:
這個方法能夠,並且是在面試中最廣泛的方法。可是請注意,咱們產生了這麼多數字卻並無使用他們,咱們計算完他們的個數後,就不再去碰了。因此我建議你們遇到這種狀況,儘可能去想一下看有沒有更好的方案。《必須掌握的 8 道數據結構面試題》你必定要看一下。
第2階:數不數數
怎麼在不產生這些數字的狀況下計算出個數?能夠作到,但須要一點點機智。注意從特定點跳出N次可以撥到的數字個數,等於從它全部臨近的點跳出N-1次可以撥到的數字個數的總和。咱們能夠表達爲這樣的遞歸關係:
若是你這樣想,就會很直觀了,跳一次時:6有3個neighbors(1,7和0),當跳0次時每一個數字自己算一次,所以每次你只能撥到3個數字。
怎麼會產生這樣機智的想法?其實,若是你學了遞歸,而且在黑板上好好研究,這一點就會變得顯而易見。這樣你就能繼續去解決這個問題,實際上就這一點就有多種實現方法,下面這個即是面試中最多見的:
就是這樣,結合這個函數計算出neighbors 就能夠了。這時候,你就能夠捏捏肩膀休息下了,由於到這裏,你已經刷掉不少人了。
接下來這個問題我常常問:這個方案的算法理論速度如何?在這個實現中,每次調用count_sequences()都會遞歸地調用count_sequences()至少2次,由於每一個數字至少有2個neighbors。這樣會致使runtime成指數增加。
對於跳1次到20次這樣的次數還能夠,可是到更大的數字,咱們就要碰壁。500次可能就須要整個宇宙的熱量來完成運算。
第3階:記憶
那麼,咱們能作的更好麼?使用上面的方法,並不能。我喜歡這個問題,也是由於他能一層一層帶出你們的智慧,找到更高效的方法。爲了找到更好的方法,讓咱們看下這個函數是怎麼調用的,以count_sequences(6, 4)爲例。注意這裏用C做爲函數名簡化。
你可能注意到了,C(6, 2)運行了3次,每次都是一樣的運算並返回一樣的值。這裏最關鍵的點在於這些重複的運算,每次你使用過他們的值以後,就沒有必要再次計算。
怎麼解決這個問題?記憶。咱們那些相同的函數調用和結果,而不是讓他們重複。這樣,在後面咱們就能夠直接給出以前的結果。實現方法以下:
第4階:動態設計
若是你再看看前面的遞歸關係,就會發現遞歸記憶的方案也有一點侷限性:
注意跳N次的結果僅僅取決於跳N-1次後調用的結果。同時,緩存中包含着每一個次數的全部結果。我之因此說這是個小侷限,由於確實不會形成真的問題,當跳的次數增加時,緩存也只是線性增加。可是,畢竟,這仍是不夠高效。《必須掌握的 8 道數據結構面試題》你必定要看一下。
怎麼辦?讓咱們再來看一看方案和代碼。注意,代碼中是從最大的次數開始,而後直接遞歸到最小的次數:
若是你把整個的函數調用圖想象成某種虛擬的樹,你就會發現咱們在執行深度優先策略。這並無什麼問題,可是它沒有利用到淺依賴這個屬性。
如何實現廣度優先策略?這裏就是一種實現方法:
這個版本比前面遞歸版好在哪裏?其實並無好不少,可是這個不是遞歸的,所以即便處理超大數據也很難崩潰。其次,它使用的是常量內存;最後,它仍舊是線性增加,即使處理200000次跳也只用不到20秒。
評估
到這裏,基本就算完了。設計並實現一個線性時的、產量內存的方案,在面試中是很是好的結果。在個人面試中,若是有面試者寫出動態編程設計,我一般會給他一個極高的評價:excellent!
當評估算法和數據結構的時候,我常常會說:面試者對問題認識清晰,而且考慮到各方面的可能,當指出不足時他也能迅速改進並提升;最終,實現了一個不錯的解決方案。
當評估代碼的時候,我最理想的說法是:面試者迅速並精確地把想法轉化爲了代碼;代碼結構嚴謹,容易閱讀。全部特殊狀況都有歸納,而且認真檢查測試了代碼,確保了沒有Bug。
總結
我知道,這個面試問題看上去彷佛有點嚇人,尤爲整個解釋下來很是繁瑣。但本文的目的和麪試中徹底不同。最後,一點面試相關的技巧,以及一些好的習慣,分享給你們:
推薦去個人博客閱讀更多:
2.Spring MVC、Spring Boot、Spring Cloud 系列教程
3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程
生活很美好,明天見~