跳動(dòng)2017秋招筆試題深度拆解:算法與基礎(chǔ)考點(diǎn)全解析)
字節(jié)跳動(dòng)2017秋招的這套開發(fā)工程師筆試卷在校招圈里流傳了很久。倒不是因?yàn)轭}目多么偏門恰恰相反它考察的內(nèi)容非?!罢薄怯?jì)算機(jī)基礎(chǔ)里的核心地帶數(shù)據(jù)結(jié)構(gòu)、算法、操作系統(tǒng)、網(wǎng)絡(luò)協(xié)議。但它的難度在于題目設(shè)計(jì)得很靈活不是死記硬背就能應(yīng)付的每一道題都在逼你展示真正的工程思維和代碼功底。我當(dāng)時(shí)刷完這套題最大的感受是它不考你“知不知道”而考你“會(huì)不會(huì)用”。很多基礎(chǔ)概念課本上背得滾瓜爛熟但放到字節(jié)的題目里就變成了需要現(xiàn)場(chǎng)推導(dǎo)、現(xiàn)場(chǎng)設(shè)計(jì)算法的實(shí)際場(chǎng)景。這份試卷的價(jià)值不在于“押題”或者“刷真題”而在于它代表了一類頭部互聯(lián)網(wǎng)公司的篩選標(biāo)準(zhǔn)。哪怕放到今天來看它的考察方向依然沒有過時(shí)扎實(shí)的編碼能力、對(duì)算法復(fù)雜度的敏感度、對(duì)系統(tǒng)底層原理的理解。這篇文章我想以這套試卷為藍(lán)本把它的考察邏輯、典型題型、以及我當(dāng)時(shí)備戰(zhàn)和復(fù)盤時(shí)的思路完整拆開來講。不論你是正在準(zhǔn)備秋招的應(yīng)屆生還是打算跳槽的工程師這份拆解應(yīng)該能幫你理清復(fù)習(xí)的優(yōu)先級(jí)。1. 試卷的整體結(jié)構(gòu)與考察邏輯1.1 一份筆試背后的篩選思路當(dāng)年字節(jié)跳動(dòng)的筆試考察的不僅是解題能力更是評(píng)估你作為開發(fā)者的“下限”在哪里。一套題通常由兩部分構(gòu)成一部分是客觀題選擇題、填空題覆蓋計(jì)算機(jī)基礎(chǔ)知識(shí)另一部分是編程題需要在線手寫完整代碼并處理輸入輸出。這種結(jié)構(gòu)直接決定了篩人的邏輯第一基礎(chǔ)知識(shí)要廣而扎實(shí)。選擇題覆蓋范圍很廣從數(shù)據(jù)結(jié)構(gòu)的時(shí)間復(fù)雜度分析到操作系統(tǒng)的進(jìn)程調(diào)度算法再到網(wǎng)絡(luò)層的擁塞控制機(jī)制都有可能出現(xiàn)。這考察的是大學(xué)四年基本功是否牢固。第二編碼能力要真刀真槍。編程題是拉開差距的核心。在線編程環(huán)境沒有IDE的代碼提示沒有編譯錯(cuò)誤的高亮輔助你需要在有限時(shí)間內(nèi)從理解題意到設(shè)計(jì)算法再到寫出無bug的代碼。這非??简?yàn)平時(shí)的代碼積累。第三工程思維要落地。部分題目特別是分值較大的編程題不是單純的算法題它會(huì)在題目中嵌入一個(gè)實(shí)際業(yè)務(wù)場(chǎng)景比如處理海量日志、設(shè)計(jì)限流策略、優(yōu)化接口響應(yīng)時(shí)間。你需要在解題過程中展示出對(duì)工程問題的分析與取舍能力。1.2 試卷內(nèi)容的模塊劃分根據(jù)我的復(fù)盤這套2017秋招的筆試卷大體上可以劃分為四個(gè)核心模塊計(jì)算機(jī)基礎(chǔ)知識(shí)模塊覆蓋數(shù)據(jù)結(jié)構(gòu)棧、隊(duì)列、二叉樹、圖、堆、操作系統(tǒng)進(jìn)程管理、內(nèi)存管理、死鎖、計(jì)算機(jī)網(wǎng)絡(luò)TCP/IP、HTTP協(xié)議。這部分難度中等但考察范圍很細(xì)有些題特別容易答錯(cuò)考驗(yàn)對(duì)細(xì)節(jié)的把握。編程語言與代碼理解模塊通常會(huì)給出一段代碼C或Java居多讓你分析輸出結(jié)果、指出錯(cuò)誤或者補(bǔ)全代碼。這部分考察實(shí)際編碼的基本功以及代碼的閱讀能力。算法設(shè)計(jì)與實(shí)現(xiàn)模塊這是筆試的重頭戲分值占比最高。??嫉念}型包括字符串處理、數(shù)組操作、動(dòng)態(tài)規(guī)劃、貪心算法、二叉樹遍歷與深搜廣搜。難點(diǎn)在于題目不會(huì)直接告訴你“用動(dòng)態(tài)規(guī)劃”而需要你從問題描述中抽象出數(shù)學(xué)模型。綜合分析與設(shè)計(jì)模塊可能以簡(jiǎn)答題形式出現(xiàn)比如“請(qǐng)?jiān)O(shè)計(jì)一個(gè)短鏈接系統(tǒng)”“如果線上服務(wù)的CPU持續(xù)飆升你如何排查”。這考察的不是背誦能力而是你面對(duì)真實(shí)問題時(shí)的分析框架和表達(dá)能力。1.3 為什么這套卷子“值得反復(fù)研究”市面上有很多筆試真題但字節(jié)2017秋招這套題我覺得非常典型原因有三第一題目設(shè)計(jì)有梯度。由淺入深一開始是基礎(chǔ)題讓大多數(shù)人能動(dòng)手后續(xù)題目難度逐漸拔高把區(qū)分度做出來。這種梯度設(shè)計(jì)對(duì)篩選人才非常有效也能讓考生快速進(jìn)入狀態(tài)。第二知識(shí)點(diǎn)考察不偏怪?;緵]有偏題、怪題所有知識(shí)點(diǎn)都在“合格計(jì)算機(jī)專業(yè)學(xué)生”的知識(shí)射程內(nèi)。不考冷門的、文檔里都難查到的細(xì)節(jié)考的是核心主干知識(shí)。第三與工程實(shí)踐結(jié)合緊密。算法題不是純粹的數(shù)學(xué)游戲而是帶業(yè)務(wù)背景的場(chǎng)景題。這使得平時(shí)注重工程實(shí)踐、有項(xiàng)目經(jīng)驗(yàn)的同學(xué)更容易脫穎而出。2. 算法與數(shù)據(jù)結(jié)構(gòu)題型的深度拆解2.1 字符串處理與雙指針技巧在筆試中字符串處理類題目出現(xiàn)頻率極高。這類題看似簡(jiǎn)單但實(shí)際編碼時(shí)很容易因?yàn)檫吔鐥l件處理不當(dāng)而出錯(cuò)。常見的考法有以下幾種字符串反轉(zhuǎn)、單詞反轉(zhuǎn)涉及空格處理、標(biāo)點(diǎn)符號(hào)處理需要借助快慢指針或雙端操作。字符串匹配與子串查找通??疾霮MP算法思想或者更簡(jiǎn)單的滑動(dòng)窗口思想。字符串去重與壓縮比如“給定一個(gè)字符串統(tǒng)計(jì)每個(gè)字符出現(xiàn)的次數(shù)并按出現(xiàn)次數(shù)排序輸出”。我印象里特別容易出錯(cuò)的地方是對(duì)字符數(shù)組與字符串結(jié)束符的處理。在C/C中字符串沒有長(zhǎng)度屬性只能依靠結(jié)尾的\0判斷循環(huán)結(jié)束一旦越界就會(huì)導(dǎo)致未定義行為。在筆試環(huán)境中很多人會(huì)遇到“本地運(yùn)行對(duì)一提交就崩潰”的情況多半就是這里出了問題。針對(duì)這類題目我的建議是在動(dòng)手寫代碼前先在草稿紙上寫下幾個(gè)典型的測(cè)試用例尤其是空字符串、只有單個(gè)字符的字符串、全部字符都相同的極端情況。這些邊界用例往往是算法是否完整的關(guān)鍵。2.2 數(shù)組操作與排序算法的變形數(shù)組題在筆試中的占比同樣很大通常來考察時(shí)間復(fù)雜度的優(yōu)化能力。例如給定一個(gè)整型數(shù)組找出其中出現(xiàn)次數(shù)超過一半的數(shù)字給定兩個(gè)有序數(shù)組找出兩個(gè)數(shù)組的中位數(shù)給定一個(gè)數(shù)組將數(shù)組中的0移動(dòng)到末尾并保持非零元素相對(duì)順序。這些題目的共同特點(diǎn)是基礎(chǔ)版本的解法并不難但想要達(dá)到最優(yōu)時(shí)間復(fù)雜度需要一點(diǎn)巧思。以“找出數(shù)組中出現(xiàn)次數(shù)超過一半的數(shù)字”為例最容易想到的解法是使用哈希表統(tǒng)計(jì)每個(gè)數(shù)字出現(xiàn)的次數(shù)時(shí)間復(fù)雜度O(n)空間復(fù)雜度O(n)。但更進(jìn)一步可以使用“摩爾投票法”將空間復(fù)雜度降低到O(1)并且編碼起來非常簡(jiǎn)潔。這類“從O(n)空間到O(1)局部”的優(yōu)化正是面試官想看到的算法思維過程。在實(shí)際筆試時(shí)我建議盡量在代碼注釋里寫下思路比如“本解法采用摩爾投票法時(shí)間復(fù)雜度O(n)空間復(fù)雜度O(1)”。這既能幫助自己理清思路也能讓閱卷人快速看見你的思考過程。2.3 動(dòng)態(tài)規(guī)劃從狀態(tài)定義到轉(zhuǎn)移方程動(dòng)態(tài)規(guī)劃是筆試中的“分水嶺”題型通常以壓軸題或高分題出現(xiàn)。它考察的不僅是算法熟練度還有問題抽象能力。常見的動(dòng)態(tài)規(guī)劃類型包括一維DP斐波那契數(shù)列、爬樓梯、最大子數(shù)組和、打家劫舍問題。二維DP編輯距離、最大正方形、不同路徑問題。區(qū)間DP回文串分割、石子合并問題。背包類DP0-1背包、完全背包及其變種。以“編輯距離”為例題目描述通常是這樣給定兩個(gè)單詞word1和word2計(jì)算將word1轉(zhuǎn)換成word2所需的最少操作數(shù)操作包括插入、刪除、替換一個(gè)字符。初看是個(gè)字符串處理問題但如果不套用DP框架很難高效解決。這類DP題的核心步驟可以歸納為三步定義狀態(tài)首先定義dp[i][j]的含義比如“word1的前i個(gè)字符轉(zhuǎn)換成word2的前j個(gè)字符所需的最小操作數(shù)”。找到狀態(tài)轉(zhuǎn)移方程分析dp[i][j]可以從哪些狀態(tài)轉(zhuǎn)移而來。對(duì)于編輯距離可以分兩種情況word1[i-1] word2[j-1]時(shí)dp[i][j] dp[i-1][j-1]否則dp[i][j] min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) 1分別對(duì)應(yīng)刪除、插入和替換三種操作。初始化與邊界條件dp[0][j] j表示從空字符串插入j個(gè)字符dp[i][0] i表示從i個(gè)字符刪除到空字符串。這塊有一個(gè)實(shí)用經(jīng)驗(yàn)在筆試中如果你能寫出正確的DP解法哪怕不是最優(yōu)解也能拿到大部分分值。很多人在考場(chǎng)上試圖追求最優(yōu)解而浪費(fèi)了太多時(shí)間最后連基礎(chǔ)解法都沒寫完這是得不償失的。2.4 二叉樹、圖論與深搜廣搜二叉樹相關(guān)的題目重點(diǎn)在遍歷方式上前序、中序、后序、層序遍歷。筆試中經(jīng)常考的有根據(jù)前序和中序遍歷重建二叉樹、二叉樹的最近公共祖先、二叉樹的序列化與反序列化等。這些題目的難點(diǎn)在于遞歸邏輯的清晰性和邊界條件的判斷。特別是“重建二叉樹”這道題很多人在遞歸參數(shù)上容易搞混因?yàn)樾枰紤]子樹區(qū)間邊界是開區(qū)間還是閉區(qū)間。我建議在考前把這類題統(tǒng)一訓(xùn)練一遍并固定下來一套自己的寫法比如統(tǒng)一使用“左閉右開”的區(qū)間表示法可以避免不少邊界錯(cuò)誤。而圖論題目最??嫉氖巧钏袲FS和廣搜BFS。常見場(chǎng)景包括島嶼數(shù)量問題、課程表拓?fù)渑判?、單詞接龍最短路徑。其中BFS常用于求解無權(quán)圖的最短路徑DFS常用于求解可達(dá)性問題或枚舉所有可能性。在筆試中圖的表示方式鄰接矩陣還是鄰接表也需要根據(jù)題目要求靈活選擇。鄰接矩陣適合稠密圖查詢兩個(gè)節(jié)點(diǎn)間是否有邊時(shí)間復(fù)雜度為O(1)鄰接表適合稀疏圖遍歷所有鄰居節(jié)點(diǎn)時(shí)更節(jié)省空間和時(shí)間。3. 計(jì)算機(jī)基礎(chǔ)知識(shí)的考點(diǎn)分布3.1 操作系統(tǒng)并發(fā)與內(nèi)存管理的考察重點(diǎn)操作系統(tǒng)是校招筆試中的“重頭戲”考核內(nèi)容集中在這幾個(gè)方面進(jìn)程與線程的區(qū)別與聯(lián)系為什么線程切換比進(jìn)程切換開銷小哪些資源是線程共享的哪些是獨(dú)享的。進(jìn)程同步與互斥信號(hào)量機(jī)制、生產(chǎn)者消費(fèi)者問題、讀者寫者問題、哲學(xué)家就餐問題。死鎖產(chǎn)生的四個(gè)必要條件互斥、占有并等待、不可剝奪、循環(huán)等待以及死鎖避免的銀行家算法。內(nèi)存管理分頁、分段、虛擬內(nèi)存、頁面置換算法LRU、FIFO、Clock。對(duì)于選擇題最容易出錯(cuò)的是“進(jìn)程與線程的對(duì)比”。很多人下意識(shí)認(rèn)為“同一進(jìn)程的多個(gè)線程共享進(jìn)程的地址空間”這個(gè)說法沒錯(cuò)但更準(zhǔn)確地說線程之間共享的是進(jìn)程的堆內(nèi)存、全局變量和打開的文件描述符而每個(gè)線程擁有獨(dú)立的??臻g和寄存器狀態(tài)。判斷題如果問“線程共享進(jìn)程的所有資源”這就是錯(cuò)誤的表述。在準(zhǔn)備這部分時(shí)我推薦把知識(shí)框架整理成對(duì)比表格來記憶。比如進(jìn)程與線程的對(duì)比、分頁與分段的對(duì)比、各種頁面置換算法的優(yōu)劣對(duì)比。表格比純文字更直觀考前翻一遍效果很好。3.2 計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議理解不能停留在背誦網(wǎng)絡(luò)部分的考法比較集中在這幾個(gè)知識(shí)點(diǎn)TCP三次握手與四次揮手為什么握手是三次而不是兩次為什么揮手需要四次TIME_WAIT狀態(tài)存在的意義是什么。TCP與UDP的區(qū)別各自的適用場(chǎng)景可靠傳輸是如何實(shí)現(xiàn)的通過序列號(hào)、確認(rèn)應(yīng)答、超時(shí)重傳。HTTP協(xié)議常見狀態(tài)碼含義200、301、302、403、404、500、502、503HTTP/1.0與HTTP/1.1的區(qū)別以及Cookie與Session的機(jī)制。DNS解析的過程從瀏覽器輸入域名到獲取IP地址經(jīng)過了哪些步驟。這些知識(shí)點(diǎn)選擇填空幾乎逢考必有。而稍微靈活一點(diǎn)的題目會(huì)要求你“分析某個(gè)網(wǎng)絡(luò)故障”。比如客戶端訪問某網(wǎng)站時(shí)非常慢可能是什么原因這類題目考察的是對(duì)TCP連接建立過程、DNS解析耗時(shí)、HTTP連接復(fù)用等知識(shí)點(diǎn)的綜合理解。我當(dāng)時(shí)復(fù)習(xí)時(shí)的一個(gè)關(guān)鍵操作是親手抓包看三次握手和四次揮手的全過程。用Wireshark或tcpdump實(shí)際上清晰地看到SYN、SYN-ACK、ACK三個(gè)報(bào)文段的序列號(hào)和標(biāo)志位變化。這個(gè)經(jīng)歷讓我對(duì)TCP狀態(tài)轉(zhuǎn)換圖的理解從一個(gè)死記硬背的流程變成了一個(gè)活生生的過程。強(qiáng)烈建議時(shí)間充裕的同學(xué)也這樣試一次。3.3 編程語言細(xì)節(jié)C與Java的高頻考點(diǎn)筆試試卷中對(duì)編程語言的考察通常以“給定小代碼段讓你判斷輸出”或“找出代碼中的錯(cuò)誤”的形式出現(xiàn)。對(duì)于C高頻考點(diǎn)集中在指針與引用的區(qū)別引用必須初始化且不能改變指向指針可以不初始化且能改變指向。內(nèi)存管理?xiàng)^(qū)與堆區(qū)的區(qū)別new/delete與malloc/free的對(duì)比。構(gòu)造函數(shù)與析構(gòu)函數(shù)拷貝構(gòu)造函數(shù)、深拷貝與淺拷貝、虛析構(gòu)函數(shù)的作用。虛函數(shù)與多態(tài)虛函數(shù)表的原理以及靜態(tài)聯(lián)編和動(dòng)態(tài)聯(lián)編的區(qū)別。對(duì)于Java高頻考點(diǎn)集中在JVM內(nèi)存區(qū)域劃分程序計(jì)數(shù)器、虛擬機(jī)棧、堆、方法區(qū)。垃圾回收機(jī)制可達(dá)性分析、引用計(jì)數(shù)法以及它的缺點(diǎn)、Java中常見的垃圾收集器。集合框架源碼細(xì)節(jié)HashMap底層是數(shù)組鏈表紅黑樹擴(kuò)容機(jī)制以及ConcurrentHashMap的鎖分段與CAS操作。多線程與并發(fā)synchronized與ReentrantLock的區(qū)別volatile關(guān)鍵字的內(nèi)存語義。這部分死記硬背是效率最低的方式因?yàn)榭挤ê莒`活。最好的復(fù)習(xí)方式是“以題帶點(diǎn)”拿各種校招真題、模擬題來做遇到一個(gè)考點(diǎn)就回去翻書或源碼把相關(guān)的底層原理串起來。比如遇到一個(gè)考HashMap的題就順手把紅黑樹的性質(zhì)、哈希沖突的處理方式、擴(kuò)容時(shí)為什么是2的冪次、在高并發(fā)下HashMap會(huì)出現(xiàn)什么問題一口氣都復(fù)習(xí)一遍效率會(huì)高出很多。4. 綜合分析與系統(tǒng)設(shè)計(jì)題的答題思路4.1 面試官到底在考什么不止是知識(shí)儲(chǔ)備這類題目分值占比可能不是最高但非常影響整體印象分。簡(jiǎn)單題的開創(chuàng)性在于它的答案沒有標(biāo)準(zhǔn)對(duì)錯(cuò)只有分析是否合理、方案是否可行、表達(dá)是否有邏輯這就考察了候選人真實(shí)的問題分析和解決框架。比如題目問“如何設(shè)計(jì)一個(gè)短鏈接系統(tǒng)”。絕大多數(shù)人看到這道題第一反應(yīng)是“生成一個(gè)短碼存儲(chǔ)到數(shù)據(jù)庫里”。但這只是最基礎(chǔ)的想法離一個(gè)可落地的系統(tǒng)設(shè)計(jì)還差得很遠(yuǎn)。一個(gè)完整的分析框架應(yīng)該包含以下部分需求分析量化系統(tǒng)有多少Q(mào)PS總數(shù)據(jù)量預(yù)估多大是讀多還是寫多這些決定了技術(shù)選型的走向。方案設(shè)計(jì)思路描述短碼生成的方案可以采用發(fā)號(hào)器自增ID轉(zhuǎn)62進(jìn)制或者隨機(jī)字符串方案并說明各自的優(yōu)缺點(diǎn)。存儲(chǔ)設(shè)計(jì)底層用什么數(shù)據(jù)庫是否需要引入緩存層如Redis來抗住高頻讀取。重定向邏輯301還是302以及為什么。容災(zāi)與擴(kuò)展性如果單機(jī)扛不住如何通過分庫分表或集群方案解決。你看哪怕是一個(gè)簡(jiǎn)短的回答也能充分展示你的全局視野。答題時(shí)可以適當(dāng)使用“首先”“其次”“最后”這么的邏輯詞讓思路更清晰。4.2 線上故障排查題的答題框架另一類??嫉木C合題是“線上故障排查”。比如“服務(wù)器CPU占用率持續(xù)100%你如何排查”。這類題目的回答要點(diǎn)在于建立一套系統(tǒng)化的排查流程。我當(dāng)時(shí)總結(jié)的通用框架是“由表及里、先資源后代碼”??梢园凑找韵马樞蛘归_確認(rèn)現(xiàn)象先通過top命令查看CPU占用率最高的進(jìn)程是哪個(gè)確認(rèn)是用戶態(tài)CPU高還是內(nèi)核態(tài)CPU高。定位線程通過ps -Lf pid查看進(jìn)程內(nèi)的線程再用jstackJava或gdbC/C查看線程棧定位到具體代碼行。分析原因是鎖競(jìng)爭(zhēng)激烈是死循環(huán)還是頻繁的GC導(dǎo)致CPU飆升解決方案針對(duì)不同的根因給出相應(yīng)的解決方案比如優(yōu)化鎖粒度、修復(fù)死循環(huán)代碼、調(diào)整JVM參數(shù)等。這個(gè)回答本身不需要特別深的技術(shù)含量但體現(xiàn)了你面對(duì)未知問題時(shí)的冷靜程度和排查邏輯。面試官可以通過這種回答判斷你在真實(shí)工作中的實(shí)戰(zhàn)能力。4.3 準(zhǔn)備方式刻意練習(xí)框架化表達(dá)這類題目對(duì)于平時(shí)只刷算法題的同學(xué)來說可能會(huì)有點(diǎn)懵。我的建議是在復(fù)習(xí)時(shí)不要只看題解要有意識(shí)地練習(xí)“把答案結(jié)構(gòu)說出來”的能力。具體操作是這樣找10道經(jīng)典的系統(tǒng)設(shè)計(jì)題包括短鏈接、秒殺系統(tǒng)、消息隊(duì)列、限流系統(tǒng)、爬蟲系統(tǒng)等把每種題型的標(biāo)準(zhǔn)回答框架寫下來然后按1分鐘、3分鐘、5分鐘三種時(shí)間長(zhǎng)度分別口頭復(fù)述一遍。這個(gè)練習(xí)看似簡(jiǎn)單但能有效訓(xùn)練你在壓力下組織答案的能力讓表達(dá)邏輯更清晰。在筆試中遇到這種題時(shí)可以先用一句話總結(jié)方案再展開細(xì)節(jié)。比如“我的方案是采用發(fā)號(hào)器生成短碼配合Redis緩存和兩級(jí)存儲(chǔ)”。這種總分結(jié)構(gòu)能讓閱卷人快速抓住重點(diǎn)避免因?yàn)榇蠖蚊枋龆Х帧?. 實(shí)戰(zhàn)模擬與備考過程中的經(jīng)驗(yàn)教訓(xùn)5.1 時(shí)間分配的黃金策略一套筆試的時(shí)間通常在60分鐘到120分鐘之間。很多同學(xué)包括我當(dāng)年都有一個(gè)通病在客觀題上糾結(jié)過久導(dǎo)致后面分值最高的編程題沒時(shí)間寫。我的建議是“先通讀整卷再按分值分配時(shí)間”。拿到試卷后花兩三分鐘通讀一遍所有題目大概標(biāo)記出每道題的難度和預(yù)估耗時(shí)。然后給自己定一條“紅線”比如選擇題部分最多不超過總時(shí)間的30%編程題部分必須保留60%以上時(shí)間。如果一道選擇題思考了超過兩分鐘還沒有思路直接先憑第一感覺選一個(gè)然后跳到下一題??陀^題不會(huì)因?yàn)槟闶强坎露鄯值噙x可能會(huì)少選得部分分而編程題如果空著那就是零分這是最致命的。5.2 編程題的“最穩(wěn)拿分三步法”編程題的評(píng)分通常不會(huì)只看最終結(jié)果是否正確運(yùn)行通過很多筆試系統(tǒng)會(huì)要求你提交完整代碼后自動(dòng)跑測(cè)試用例。但為了保險(xiǎn)起見我建議大家把編程題當(dāng)作小型工程來對(duì)待并采用這套“最穩(wěn)三步法”第一步先讀懂題意提煉輸入輸出格式。很多編程題會(huì)給出非常長(zhǎng)的題干但核心要求其實(shí)就是一句話。先把輸入是什么、輸出是什么搞清楚然后再動(dòng)手。第二步設(shè)計(jì)算法寫下核心偽代碼。不要一上來就寫完整代碼。先用偽代碼或注釋把思路寫下來比如“需要先排序然后雙指針找目標(biāo)值”。這一步能幫你避免在后半段代碼中迷失方向。第三步實(shí)現(xiàn)代碼并逐行檢查邊界。寫完代碼之后不要急著提交先用幾組自測(cè)用例在腦中模擬運(yùn)行一遍特別是空輸入、超大輸入、重復(fù)數(shù)據(jù)這些邊界場(chǎng)景。這個(gè)檢查習(xí)慣能幫你攔截掉大部分低級(jí)錯(cuò)誤。5.3 我踩過的一個(gè)典型“坑”我當(dāng)年在真正參加筆試時(shí)犯過一個(gè)特別低級(jí)的錯(cuò)誤編程題中輸入是一個(gè)包含空格的字符串我當(dāng)時(shí)用cin str來讀取導(dǎo)致字符串讀到空格就截?cái)嗔?。結(jié)果后面對(duì)字符串的所有處理邏輯全部跑偏調(diào)試了半天才發(fā)現(xiàn)問題所在。如果當(dāng)時(shí)先用getline(cin, str)讀取就不會(huì)出現(xiàn)這個(gè)問題。這個(gè)經(jīng)歷告訴我兩個(gè)重要教訓(xùn)第一筆試前一定要熟悉在線評(píng)測(cè)系統(tǒng)常用的輸入輸出方式尤其是字符串讀取第二不要忽略基礎(chǔ)API的細(xì)節(jié)你自認(rèn)為“用得滾瓜爛熟”的函數(shù)在邊界情況下可能完全不符合預(yù)期。另外還有一個(gè)非常實(shí)用的經(jīng)驗(yàn)如果筆試系統(tǒng)支持本地編譯器調(diào)試那就先在本地寫好代碼并運(yùn)行測(cè)試用例再?gòu)?fù)制到在線編輯器。本地調(diào)試能打印中間變量直觀看到算法每一步的狀態(tài)在刷題練習(xí)時(shí)一定要養(yǎng)成這個(gè)習(xí)慣。5.4 高頻錯(cuò)誤速查表錯(cuò)誤類型典型場(chǎng)景規(guī)避方法數(shù)組越界遍歷數(shù)組時(shí)循環(huán)條件寫作 i n統(tǒng)一采用 i n 的寫法循環(huán)前先檢查邊界字符串讀取截?cái)噍斎牒崭竦淖址畷r(shí)用了 cin使用 getline 或 fgets 讀取整行遞歸棧溢出深搜樹或圖時(shí)遞歸深度過大改用顯式棧模擬遞歸或檢查遞歸終止條件空指針/空對(duì)象對(duì)可能為空的鏈表、樹節(jié)點(diǎn)直接訪問屬性單步模板代碼先判空動(dòng)態(tài)規(guī)劃數(shù)組初始化錯(cuò)誤忘記了 dp[0] 或 dp[i][0] 行的初始化初始化三步法從邊界行和邊界列逐一驗(yàn)證這張表是我在刷題過程中逐漸總結(jié)出來的每次筆試前我都會(huì)快速過一遍提醒自己避開這些最常見的低級(jí)錯(cuò)誤。6. 備考規(guī)劃與筆試技巧6.1 刷題路線系統(tǒng)化比題海更重要在備考時(shí)間有限的情況下盲目刷題效率很低。我建議按照以下路線進(jìn)行系統(tǒng)性復(fù)習(xí)第一周基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)與算法。重點(diǎn)刷數(shù)組、鏈表、棧、隊(duì)列、哈希表、二叉樹相關(guān)的基礎(chǔ)題恢復(fù)代碼手感。第二周算法思想專項(xiàng)。集中攻堅(jiān)分治、二分查找、滑動(dòng)窗口、雙指針、回溯算法并開始涉獵動(dòng)態(tài)規(guī)劃的基礎(chǔ)題型。第三周動(dòng)態(tài)規(guī)劃與圖論。這個(gè)階段要多做DP題每天保證至少3道DP題目的訓(xùn)練量同時(shí)穿插圖論的BFS/DFS練習(xí)。第四周真題模擬與查漏補(bǔ)缺。按照筆試的時(shí)間和題量要求做2到3套完整的模擬卷提前適應(yīng)考場(chǎng)節(jié)奏。這個(gè)過程看起來很常規(guī)但真正執(zhí)行的人不多。很多人刷題是“隨緣刷”今天想起來做兩道明天忙就停了這樣很難形成體系化的知識(shí)網(wǎng)絡(luò)考試時(shí)容易“似曾相識(shí)但就是寫不出來”。6.2 每個(gè)考點(diǎn)復(fù)習(xí)到什么程度才算合格有一個(gè)自測(cè)標(biāo)準(zhǔn)我可以分享給大家數(shù)據(jù)結(jié)構(gòu)能夠不看代碼在白紙上畫出二叉樹的前中后序遍歷結(jié)果能手動(dòng)模擬哈希表插人沖突的解決過程。算法能夠用口頭語言講清楚二分查找的循環(huán)不變式能夠解釋為什么某些場(chǎng)景下貪心算法不適用。操作系統(tǒng)能在白板上畫出進(jìn)程狀態(tài)轉(zhuǎn)換圖并說明每個(gè)轉(zhuǎn)換發(fā)生的條件。網(wǎng)絡(luò)能夠畫出TCP連接建立與釋放的時(shí)序圖并標(biāo)注每一方的狀態(tài)變化。如果以上幾點(diǎn)你都能做到那筆試的基礎(chǔ)題和中等題基本穩(wěn)了。如果還有說不清楚的地方說明這個(gè)知識(shí)點(diǎn)你是“死記硬背”的建議重新看書或者找視頻理解一遍。6.3 考前一周與考前一晚的實(shí)戰(zhàn)建議考前一周不要再去鉆研難題和偏題了。這個(gè)階段的首要任務(wù)是保持手感而不是提升能力。我的做法是每天做兩道中等難度的算法題保持編碼的熟練度并且把之前整理好的知識(shí)框架表、易錯(cuò)點(diǎn)清單拿出來反復(fù)看??记耙煌碛绕湟⒁馑邥r(shí)間。很多筆試是在線進(jìn)行的對(duì)精神狀態(tài)的要求非常高。我見過太多同學(xué)因?yàn)榘疽箯?fù)習(xí)結(jié)果筆試時(shí)頭腦發(fā)脹連簡(jiǎn)單的二分查找都寫錯(cuò)。與其多復(fù)習(xí)一晚上不如好好睡一覺保持清醒的頭腦上考場(chǎng)。另外考前一天一定要檢查好設(shè)備和網(wǎng)絡(luò)。提前測(cè)試編輯器能否正常使用編譯器版本是否匹配網(wǎng)絡(luò)是否穩(wěn)定。這些看起來瑣碎的事情一旦出了問題會(huì)成為整場(chǎng)考試中最致命的干擾因素。7. 筆試之外這套試卷帶來的長(zhǎng)遠(yuǎn)思考當(dāng)我復(fù)盤完這份2017年秋招筆試卷的全部考點(diǎn)后我發(fā)現(xiàn)一個(gè)很有意思的現(xiàn)象整張?jiān)嚲砗苌儆屑兇獾摹坝洃涱}”每一道題都在考察某種能力而不僅僅是一段知識(shí)??陀^題考察的是你“知識(shí)的組織方式”是否形成體系。編程題考察的是你“將抽象問題轉(zhuǎn)化為代碼”的能力。綜合題考察的是你“面對(duì)開放性問題時(shí)的思考路徑”。這種考察邏輯和大學(xué)的期末考試有著本質(zhì)區(qū)別它更接近真實(shí)工作中你解決一個(gè)未知問題的過程。哪怕是現(xiàn)在我已經(jīng)工作好幾年了回看這些考點(diǎn)發(fā)現(xiàn)它們依然是日常開發(fā)中天天要用的底層能力。所以我真心建議正在準(zhǔn)備筆試的同學(xué)不要只把這套試卷當(dāng)成“敲門磚”來對(duì)待。把它當(dāng)成一個(gè)檢測(cè)自身能力短板的機(jī)會(huì)針對(duì)弱項(xiàng)進(jìn)行系統(tǒng)性提升。筆試只是第一關(guān)即使僥幸通過后續(xù)的面試也會(huì)問到類似的問題甚至考察得更深。如果你能從筆試備考中真正建立起一套完整的知識(shí)體系那收獲的遠(yuǎn)遠(yuǎn)不止是一份offer。最后再分享一個(gè)我個(gè)人的操作習(xí)慣每次筆試或面試結(jié)束后我都會(huì)在當(dāng)天晚上立刻寫下復(fù)盤筆記記錄哪些題答得順暢、哪些題卡住了、卡住的根本原因是什么。半個(gè)月后我會(huì)重新做一遍卡住過的題目直到能順暢寫出最優(yōu)解為止。這個(gè)習(xí)慣讓我在連續(xù)多場(chǎng)面試中狀態(tài)越來越穩(wěn)定因?yàn)槲抑雷约好看味荚谘a(bǔ)漏而不是原地打轉(zhuǎn)。希望這個(gè)經(jīng)驗(yàn)對(duì)你也有用。