如何成為一名Google工程師
[譯] Google Interview University 一套完整的學習手冊幫助自己準備 Google 的面試
這是?
這是我為了從 web 開發者(自學、非電腦科學學位)蛻變至 Google 軟體工程師所制定的計劃,其內容歷時數月。
這一長列表是從 Google 的指導筆記 中萃取出來並進行擴充套件。因此,有些事情你必須去了解一下。我在列表的底部添加了一些額外項,用於解決面試中可能會出現的問題。這些額外項大部分是來自於 Steve Yegge 的“得到在 Google 工作的機會”。而在 Google 指導筆記的逐字間,它們有時也會被反映出來。
目錄
—————- 下面的內容是可選的 —————-
為何要用到它?
我一直都是遵循該計劃去準備 Google 的面試。自 1997 年以來,我一直從事於 web 程式的構建、伺服器的構建及創業型公司的創辦。對於只有著一個經濟學學位,而不是電腦科學學位(CS degree)的我來說,在職業生涯中所取得的都非常成功。然而,我想在 Google 工作,並進入大型系統中,真正地去理解計算機系統、演算法效率、資料結構效能、低級別程式語言及其工作原理。可一項都不瞭解的我,怎麼會被 Google 所應聘呢?
當我建立該專案時,我從一個堆疊到一個堆都不瞭解。那時的我,完全不瞭解 Big-O 、樹,或如何去遍歷一個圖。如果非要我去編寫一個排序演算法的話,我只能說我所寫的肯定是很糟糕。一直以來,我所用的任何資料結構都是內建於程式語言當中。至於它們在背後是如何運作,對此我一概不清楚。此外,以前的我並不需要對記憶體進行管理,最多就只是在一個正在執行的程序丟擲了“記憶體不足”的錯誤後,採取一些權變措施。而在我的程式設計生活中,也甚少使用到多維陣列,可關聯陣列卻成千上萬。而且,從一開始到現在,我都還未曾自己實現過資料結構。
就是這樣的我,在經過該學習計劃後,已然對被 Google 所僱傭充滿信心。這是一個漫長的計劃,以至於花費了我數月的時間。若您早已熟悉大部分的知識,那麼也許能節省大量的時間。
如何使用它
下面所有的東西都只是一個概述。因此,你需要由上而下逐一地去處理它。
在學習過程中,我是使用 GitHub 特殊的語法特性 markdown flavor 去檢查計劃的進展,包括使用任務列表。
- [x] 建立一個新的分支,以使得你可以像這樣去檢查計劃的進展。直接往方括號中填寫一個字元 x 即可:[x]
擁有一名 Googler 的心態
把一個(或兩個)印有“future Googler
我得到了工作嗎?
我還沒去應聘。
因為我離完成學習(完成該瘋狂的計劃列表)還需要數天的時間,並打算在下週開始用一整天的時間,以程式設計的方式去解決問題。當然,這將會持續數週的時間。然後,我才通過使用在二月份所得到的一個介紹資格,去正式應聘 Google(沒錯,是二月份時就得到的)。
感謝 JP 的這次介紹。
跟隨著我
目前我仍在該計劃的執行過程中,如果你想跟隨我腳步去學習的話,可以登進我在 GoogleyAsHeck.com 上所寫的部落格。
下面是我的聯絡方式:
不要自以為自己不夠聰明
- Google 的工程師都是才智過人的。但是,就算是工作在 Google 的他們,仍然會因為自己不夠聰明而感到一種不安。
關於 Google
相關視訊資源
部分視訊只能通過在 Coursera、Edx 或 Lynda.com class 上註冊登入才能觀看。這些視訊被稱為網路公開課程(MOOC)。即便是免費觀看,部分課程可能會由於不在時間段內而無法獲取。因此,你需要多等待幾個月。
很感謝您能幫我把網路公開課程的視訊連結轉換成公開的視訊源,以代替那些線上課程的視訊。此外,一些大學的講座視訊也是我所青睞的。
面試過程 & 通用的面試準備
為你的面試選擇一種語言
在大多數公司的面試當中,你可以在程式設計這一環節,使用一種自己用起來較為舒適的語言去完成程式設計。但在 Google,你只有三種固定的選擇:
- C++
- Java
- Python
有時你也可以使用下面兩種,但需要事先查閱說明。因為,說明中會有警告:
- JavaScript
- Ruby
你需要對你所選擇的語言感到非常舒適且足夠了解。
更多關於語言選擇的閱讀:
由於,我正在學習C、C++ 和 Python。因此,在下面你會看到部分關於它們的學習資料。相關書籍請看文章的底部。
在你開始之前
該列表已經持續更新了很長的一段時間,所以,我們的確很容易會對其失去控制。
這裡列出了一些我所犯過的錯誤,希望您不要重滔覆轍。
1. 你不可能把所有的東西都記住
就算我查看了數小時的視訊,並記錄了大量的筆記。幾個月後的我,仍然會忘卻其中大部分的東西。所以,我翻閱了我的筆記,並將可回顧的東西製作成抽認卡(flashcard)(請往下看)
2. 使用抽認卡
為了解決善忘的問題,我製作了一些關於抽認卡的頁面,用於新增兩種抽認卡:正常的及帶有程式碼的。每種卡都會有不同的格式設計。
而且,我還以移動裝置為先去設計這些網頁,以使得在任何地方的我,都能通過我的手機及平板去回顧知識。
你也可以免費製作屬於你自己的抽認卡網站:
- 我的抽認卡資料庫:有一點需要記住的是,我做事有點過頭,以至於把卡片都覆蓋到所有的東西上。從組合語言和 Python 的細枝末節,乃至到機器學習和統計都被覆蓋到卡片上。而這種做法,對於 Google 的要求來說,卻是多餘。
在抽認卡上做筆記: 若你第一次發現你知道問題的答案時,先不要急著把其標註成“已懂”。你需要做的,是去檢視一下是否有同樣的抽認卡,並在你真正懂得如何解決問題之前,多問自己幾次。重複地問答可幫助您深刻記住該知識點。
3. 回顧,回顧,回顧
我留有一組 ASCII 碼錶、OSI 堆疊、Big-O 記號及更多的小抄紙,以便在空餘的時候可以學習。
每程式設計半個小時就要休息一下,並去回顧你的抽認卡。
4. 專注
在學習的過程中,往往會有許多令人分心的事佔據著我們寶貴的時間。因此,專注和集中注意力是非常困難的。
你所看不到的
由於,這個巨大的列表一開始是作為我個人從 Google 面試指導筆記所形成的一個事件處理列表。因此,有一些我熟悉且普遍的技術在此都未被談及到:
- SQL
- Javascript
- HTML、CSS 和其他前端技術
日常計劃
部分問題可能會花費一天的時間去學習,而部分則會花費多天。當然,有些學習並不需要我們懂得如何實現。
因此,每一天我都會在下面所列出的列表中選擇一項,並檢視相關的視訊。然後,使用以下的一種語言去實現:
C —— 使用結構體和函式,該函式會接受一個結構體指標 * 及其他資料作為引數。
C++ —— 不使用內建的資料型別。
C++ —— 使用內建的資料型別,如使用 STL 的 std::list 來作為連結串列。
Python —— 使用內建的資料型別(為了持續練習 Python),並編寫一些測試去保證自己程式碼的正確性。有時,只需要使用斷言函式 assert() 即可。
此外,你也可以使用 Java 或其他語言。以上只是我的個人偏好而已。
為何要在這些語言上分別實現一次?
因為可以練習,練習,練習,直至我厭倦它,並完美地實現出來。(若有部分邊緣條件沒想到時,我會用書寫的形式記錄下來並去記憶)
因為可以在純原生的條件下工作(不需垃圾回收機制的幫助下,分配/釋放記憶體(除了 Python))
因為可以利用上內建的資料型別,以使得我擁有在現實中使用內建工具的經驗(在生產環境中,我不會去實現自己的連結串列)
就算我沒有時間去每一項都這麼做,但我也會盡我所能的。
在這裡,你可以檢視到我的程式碼:
- C
- C++
- Python
你不需要記住每一個演算法的內部原理。
在一個白板上寫程式碼,而不要直接在計算機上編寫。在測試完部分簡單的輸入後,到計算機上再測試一遍。
必備知識
演算法複雜度 / Big-O / 漸進分析法
資料結構
陣列(Arrays)
- 實現一個可自動調整大小的動態陣列。
- [ ] 實現一個動態陣列(可自動調整大小的可變陣列):
- [ ] 練習使用陣列和指標去編碼,並且指標是通過計算去跳轉而不是使用索引
- [ ] 通過分配記憶體來新建一個原生資料型陣列
- 可以使用 int 型別的陣列,但不能使用其語法特性
- 從大小為16或更大的數(使用2的倍數 —— 16、32、64、128)開始編寫
- [ ] size() —— 陣列元素的個數
- [ ] capacity() —— 可容納元素的個數
- [ ] is_empty()
- [ ] at(index) —— 返回對應索引的元素,且若索引越界則憤然報錯
- [ ] push(item)
- [ ] insert(index, item) —— 在指定索引中插入元素,並把後面的元素依次後移
- [ ] prepend(item) —— 可以使用上面的 insert 函式,傳參 index 為 0
- [ ] pop() —— 刪除在陣列末端的元素,並返回其值
- [ ] delete(index) —— 刪除指定索引的元素,並把後面的元素依次前移
- [ ] remove(item) —— 刪除指定值的元素,並返回其索引(即使有多個元素)
- [ ] find(item) —— 尋找指定值的元素並返回其中第一個出現的元素其索引,若未找到則返回 -1
- [ ] resize(new_capacity) // 私有函式
- 若陣列的大小到達其容積,則變大一倍
- 獲取元素後,若陣列大小為其容積的1/4,則縮小一半
- [ ] 時間複雜度
- 在陣列末端增加/刪除、定位、更新元素,只允許佔 O(1) 的時間複雜度(平攤(amortized)去分配記憶體以獲取更多空間)
- 在陣列任何地方插入/移除元素,只允許 O(n) 的時間複雜度
- [ ] 空間複雜度
- 因為在記憶體中分配的空間鄰近,所以有助於提高效能
- 空間需求 = (大於或等於 n 的陣列容積)* 元素的大小。即便空間需求為 2n,其空間複雜度仍然是 O(n)
連結串列(Linked Lists)
- [ ] C 程式碼(視訊)
- 並非看完整個視訊,只需要看關於節點結果和記憶體分配那一部分即可
- [ ] 的確:你需要關於“指向指標的指標”的相關知識:(因為當你傳遞一個指標到一個函式時,該函式可能會改變指標所指向的地址)該頁只是為了讓你瞭解“指向指標的指標”這一概念。但我並不推薦這種鏈式遍歷的風格。因為,這種風格的程式碼,其可讀性和可維護性太低。
- [ ] 實現(我實現了使用尾指標以及沒有使用尾指標這兩種情況):
- [ ] size() —— 返回連結串列中資料元素的個數
- [ ] empty() —— 若連結串列為空則返回一個布林值 true
- [ ] value_at(index) —— 返回第 n 個元素的值(從0開始計算)
- [ ] push_front(value) —— 新增元素到連結串列的首部
- [ ] pop_front() —— 刪除首部元素並返回其值
- [ ] push_back(value) —— 新增元素到連結串列的尾部
- [ ] pop_back() —— 刪除尾部元素並返回其值
- [ ] front() —— 返回首部元素的值
- [ ] back() —— 返回尾部元素的值
- [ ] insert(index, value) —— 插入值到指定的索引,並把當前索引的元素指向到新的元素
- [ ] erase(index) —— 刪除指定索引的節點
- [ ] value_n_from_end(n) —— 返回倒數第 n 個節點的值
- [ ] reverse() —— 逆序連結串列
- [ ] remove_value(value) —— 刪除連結串列中指定值的第一個元素
- [ ] 雙向連結串列
- [ ] C 程式碼(視訊)
堆疊(Stack)
佇列(Queue)
- [ ] 使用含有尾部指標的連結串列來實現:
- enqueue(value) —— 在尾部新增值
- dequeue() —— 刪除最早新增的元素並返回其值(首部元素)
- empty()
- [ ] 使用固定大小的陣列實現:
- enqueue(value) —— 在可容的情況下新增元素到尾部
- dequeue() —— 刪除最早新增的元素並返回其值
- empty()
- full()
- [ ] 花銷:
- 在糟糕的實現情況下,使用連結串列所實現的佇列,其入列和出列的時間複雜度將會是 O(n)。因為,你需要找到下一個元素,以致迴圈整個佇列
- enqueue:O(1)(平攤(amortized)、連結串列和陣列 [探測(probing)])
- dequeue:O(1)(連結串列和陣列)
- empty:O(1)(連結串列和陣列)
- [ ] 使用含有尾部指標的連結串列來實現:
更多的知識
樹(Trees)
樹 —— 筆記 & 背景
- 基本的樹形結構
- 遍歷
- 操作演算法
- BFS(廣度優先檢索,breadth-first search)
- 層序遍歷(使用佇列的 BFS 演算法)
- 時間複雜度: O(n)
- 空間複雜度:
- 最好情況: O(1)
- 最壞情況:O(n/2)=O(n)
- 層序遍歷(使用佇列的 BFS 演算法)
- DFS(深度優先檢索,depth-first search)
- 筆記:
- 時間複雜度:O(n)
- 空間複雜度:
- 最好情況:O(log n) - 樹的平均高度
- 最壞情況:O(n)
- 中序遍歷(DFS:左、節點本身、右)
- 後序遍歷(DFS:左、右、節點本身)
- 先序遍歷(DFS:節點本身、左、右)
- 筆記:
二叉查詢樹(Binary search trees):BSTs
堆(Heap) / 優先順序佇列(Priority Queue) / 二叉堆(Binary Heap)
- 視覺化是一棵樹,但通常是以線性的形式儲存(陣列、連結串列)
- [ ] 堆
- [ ] 實現一個大頂堆:
- [ ] insert
- [ ] sift_up —— 用於插入元素
- [ ] get_max —— 返回最大值但不移除元素
- [ ] get_size() —— 返回儲存的元素數量
- [ ] is_empty() —— 若堆為空則返回 true
- [ ] extract_max —— 返回最大值並移除
- [ ] sift_down —— 用於獲取最大值元素
- [ ] remove(i) —— 刪除指定索引的元素
- [ ] heapify —— 構建堆,用於堆排序
- [ ] heap_sort() —— 拿到一個未排序的陣列,然後使用大頂堆進行就地排序
- 注意:若用小頂堆可節省操作,但導致空間複雜度加倍。(無法做到就地)
平衡查詢樹(Balanced search trees)
- 掌握至少一種平衡查詢樹(並懂得如何實現):
- “在各種平衡查詢樹當中,AVL 樹和2-3樹已經成為了過去,而紅黑樹(red-black trees)看似變得越來越受人青睞。這種令人特別感興趣的資料結構,亦稱伸展樹(splay tree)。它可以自我管理,且會使用輪換來移除任何訪問過根節點的 key。” —— Skiena
- 因此,在各種各樣的平衡查詢樹當中,我選擇了伸展樹來實現。雖然,通過我的閱讀,我發現在 Google 的面試中並不會被要求實現一棵平衡查詢樹。但是,為了勝人一籌,我們還是應該看看如何去實現。在閱讀了大量關於紅黑樹的程式碼後,我才發現伸展樹的實現確實會使得各方面更為高效。
- 伸展樹:插入、查詢、刪除函式的實現,而如果你最終實現了紅黑樹,那麼請嘗試一下:
- 跳過刪除函式,直接實現搜尋和插入功能
- 我希望能閱讀到更多關於 B 樹的資料,因為它也被廣泛地應用到大型的資料庫當中。
[ ] AVL 樹
- 實際中:我能告訴你的是,該種樹並無太多的用途,但我能看到有用的地方在哪裡:AVL 樹是另一種平衡查詢樹結構。其可支援時間複雜度為 O(log n) 的查詢、插入及刪除。它比紅黑樹嚴格意義上更為平衡,從而導致插入和刪除更慢,但遍歷卻更快。正因如此,才彰顯其結構的魅力。只需要構建一次,就可以在不重新構造的情況下讀取,適合於實現諸如語言字典(或程式字典,如一個彙編程式或解釋程式的操作碼)。
- [ ] 分離與合併
[ ] 伸展樹
- 實際中:伸展樹一般用於快取、記憶體分配者、路由器、垃圾回收者、資料壓縮、ropes(字串的一種替代品,用於儲存長串的文字字元)、Windows NT(虛擬記憶體、網路及檔案系統)等的實現。
- [ ] MIT 教程:伸展樹(Splay trees):
- 該教程會過於學術,但請觀看到最後的10分鐘以確保掌握。
- 視訊
[ ] 2-3查詢樹
- 實際中:2-3樹的元素插入非常快速,但卻有著查詢慢的代價(因為相比較 AVL 樹來說,其高度更高)。
- 你會很少用到2-3樹。這是因為,其實現過程中涉及到不同型別的節點。因此,人們更多地會選擇紅黑樹。
[ ] 2-3-4樹 (亦稱2-4樹)
- 實際中:對於每一棵2-4樹,都有著對應的紅黑樹來儲存同樣順序的資料元素。在2-4樹上進行插入及刪除操作等同於在紅黑樹上進行顏色翻轉及輪換。這使得2-4樹成為一種用於掌握紅黑樹背後邏輯的重要工具。這就是為什麼許多演算法引導文章都會在介紹紅黑樹之前,先介紹2-4樹,儘管2-4樹在實際中並不經常使用。
[ ] B 樹
- 有趣的是:為啥叫 B 仍然是一個神祕。因為 B 可代表波音(Boeing)、平衡(Balanced)或 Bayer(聯合創造者)
- 實際中:B 樹會被廣泛適用於資料庫中,而現代大多數的檔案系統都會使用到這種樹(或變種)。除了運用在資料庫中,B 樹也會被用於檔案系統以快速訪問一個檔案的任意塊。但存在著一個基本的問題,那就是如何將檔案塊 i 轉換成一個硬碟塊(或一個柱面-磁頭-扇區)上的地址。
- [ ] B 樹
- [ ] MIT 6.851 —— 記憶體層次模組(Memory Hierarchy Models)(視訊)
- 覆蓋有快取記憶體引數無關型(cache-oblivious)B 樹和非常有趣的資料結構
- 頭37分鐘講述的很專業,或許可以跳過(B 指塊的大小、即快取行的大小)
[ ] 紅黑樹
- 實際中:紅黑樹提供了在最壞情況下插入操作、刪除操作和查詢操作的時間保證。這些時間值的保障不僅對時間敏感型應用有用,例如實時應用,還對在其他資料結構中塊的構建非常有用,而這些資料結構都提供了最壞情況下的保障;例如,許多用於計算幾何學的資料結構都可以基於紅黑樹,而目前 Linux 系統所採用的完全公平排程器(the Completely Fair Scheduler)也使用到了該種樹。在 Java 8中,紅黑樹也被用於儲存雜湊列表集合中相同的資料,而不是使用連結串列及雜湊碼。
N 叉樹(K 叉樹、M 叉樹)
- 注意:N 或 K 指的是分支系數(即樹的最大分支數):
- 二叉樹是一種分支系數為2的樹
- 2-3樹是一種分支系數為3的樹
- [ ] K 叉樹
- 注意:N 或 K 指的是分支系數(即樹的最大分支數):
排序(Sorting)
圖(Graphs)
圖論能解決計算機科學裡的很多問題,所以這一節會比較長,像樹和排序的部分一樣。
可以從 Skiena 的書(參考下面的書推薦小節)和麵試書籍中學習更多關於圖的實踐。
更多知識
終面
這一部分有一些短視訊,你可以快速的觀看和複習大多數重要概念。
這對經常性的鞏固很有幫助。
綜述:
- [ ] 2-3 分鐘的短視訊系列 (23 個)
- [ ] 2-5 分鐘的短視訊系列 - Michael Sambol (18 個):
排序:
書籍
Google Coaching 裡提到的
閱讀並做練習:
[ ] 演算法設計手冊 (Skiena)
read and do exercises from the books below. Then move to coding challenges (further down below)
一旦你理解了每日計劃裡的所有內容,就去讀上面所列的書並完成練習,然後開始讀下面所列的書並做練習,之後就可以開始實戰寫程式碼了(本文再往後的部分)
然後閱讀 (這本獲得了很多推薦, 但是不在 Google coaching 的文件裡):
- [ ] Cracking the Coding Interview, 6th Edition
- 如果你看到有人在看 “The Google Resume”, 實際上它和 “Cracking the Coding Interview” 是同一個作者寫的,而且後者是升級版。
附加書單
這些沒有被 Google 推薦閱讀,不過我因為需要這些背景知識所以也把它們列在了這裡。
如果你有時間
編碼練習和挑戰
一旦你學會了理論基礎,就應該把它們拿出來練練。
儘量堅持每天做編碼練習,越多越好。
程式設計問題預備:
編碼練習平臺:
當你臨近面試時
你的簡歷
當面試來臨的時候
隨著下面列舉的問題思考下你可能會遇到的 20 個面試問題
每個問題準備 2-3 種回答
準備點故事,不要只是擺一些你完成的事情的資料,相信我,人人都喜歡聽故事
- 你為什麼想得到這份工作?
- 你解決過的最有難度的問題是什麼?
- 面對過的最大挑戰是什麼?
- 見過的最好或者最壞的設計是怎麼樣的?
- 對某項 Google 產品提出改進建議。
- 你作為一個個體同時也是團隊的一員,如何達到最好的工作狀態?
- 你的什麼技能或者經驗是你的角色中不可或缺的?為什麼?
- 你在某份工作或某個專案中最享受的是什麼?
- 你在某份工作或某個專案中面臨過的最大挑戰是什麼?
- 你在某份工作或某個專案中遇到過的最蛋疼的 Bug 是什麼樣的?
- 你在某份工作或某個專案中學到了什麼?
- 你在某份工作或某個專案中哪些地方還可以做的更好?
問面試官的問題
我會問的一些:(可能我已經知道了答案但我想聽聽面試官的看法或者瞭解團隊的前景):
- 團隊多大規模?
- 開發週期是怎樣的? 會使用瀑布流/極限程式設計/敏捷開發麼?
- 經常會為 deadline 加班麼? 或者是有彈性的?
- 團隊裡怎麼做技術選型?
- 每週平均開多少次會?
- 你覺得工作環境有助於員工集中精力嗎?
- 目前正在做什麼工作?
- 喜歡這些事情嗎?
- 工作期限是怎麼樣的?
當你獲得了夢想的職位
我還能說些什麼呢,恭喜你!
堅持繼續學習。
得到這份工作只是一個開始。
*****************************************************************************************************
*****************************************************************************************************
下面的內容都是可選的。這些是我的推薦,不是 Google 的。
通過學習這些內容,你將會得到更多的有關 CS 的概念,並將為所有的軟體工程工作做更好的準備。
*****************************************************************************************************
*****************************************************************************************************
附加的學習
–
一些主題的額外內容
我為前面提到的某些主題增加了一些額外的內容,之所以沒有直接新增到前面,是因為這樣很容易導致某個主題內容過多。畢竟你想在本世紀找到一份工作,對吧?
視訊系列
坐下來享受一下吧。”netflix and skill” :P