據(jù)結(jié)構(gòu)到斷點續(xù)傳的備考指南)
2018年那一輪迅雷校園招聘的客戶端在線筆試我到現(xiàn)在還記得拿到試卷時的感受選擇題量不小編程題不是純粹的LeetCode風(fēng)格而是帶業(yè)務(wù)場景的。當(dāng)時用的在線筆試平臺會實時倒計時兩個多小時看著多真正動起手來才發(fā)現(xiàn)每一段都要精打細(xì)算。考的內(nèi)容橫跨數(shù)據(jù)結(jié)構(gòu)、網(wǎng)絡(luò)、并發(fā)和客戶端基礎(chǔ)幾乎把計算機專業(yè)核心課都過了一遍。這篇文章不打算復(fù)述原題筆試題目本身有保密要求而且過了這么多年逐字回憶也沒有意義。我想做的是把這一類下載工具廠商客戶端崗的筆試邏輯拆開來講它為什么考這些、高頻考點背后對應(yīng)什么能力模型、編程題拿到手應(yīng)該按什么順序思考、以及那些你準(zhǔn)備LeetCode時根本不會注意到的坑。對準(zhǔn)備迅雷以及騰訊、網(wǎng)易這類有重客戶端業(yè)務(wù)公司校招筆試的同學(xué)來說應(yīng)該能直接派上用場。1. 迅雷這份筆試卷的出題邏輯客戶端崗位要的不是刷題機器先說一個很多人對筆試的誤解以為刷題量夠了就能過。放在純算法崗或許成立但迅雷這種以下載工具起家的公司客戶端崗筆試的底層邏輯完全不一樣。它考的不是你能不能做出難題而是你能不能建立起從業(yè)務(wù)場景到技術(shù)方案的映射能力。1.1 迅雷客戶端崗位的核心能力模型迅雷做的是下載工具客戶端形態(tài)覆蓋Windows、macOS、移動端。這類產(chǎn)品的核心技術(shù)痛點是大文件傳輸、弱網(wǎng)環(huán)境下的穩(wěn)定性、多任務(wù)并發(fā)調(diào)度、磁盤讀寫優(yōu)化、以及長時間運行下的內(nèi)存控制。這就決定了它的客戶端崗位在選人時最看重的不是你會多少冷門算法而是下面四層能力第一層是計算機基礎(chǔ)包括數(shù)據(jù)結(jié)構(gòu)、操作系統(tǒng)、計算機網(wǎng)絡(luò)這是筆試選擇題的主戰(zhàn)場也是后續(xù)所有技術(shù)討論的地基。第二層是網(wǎng)絡(luò)編程能力TCP/UDP協(xié)議細(xì)節(jié)、HTTP協(xié)議擴展頭、斷點續(xù)傳機制、P2P通信模型這些東西在迅雷的實際業(yè)務(wù)里每天都在被調(diào)用。第三層是并發(fā)處理能力多線程下載、線程池調(diào)度、任務(wù)隊列、鎖與同步下載器本質(zhì)上就是一個高并發(fā)的任務(wù)調(diào)度系統(tǒng)。第四層才是客戶端工程化能力包括內(nèi)存管理、UI渲染優(yōu)化、緩存策略。如果你只看前三層會覺得這是通用后臺崗的考核范圍這也正是迅雷筆試比較特別的地方它把網(wǎng)絡(luò)和并發(fā)的權(quán)重抬得非常高因為下載場景天然依賴這兩塊知識。1.2 題型結(jié)構(gòu)與時間分配2018年那場筆試是線上進行整體結(jié)構(gòu)大概是單選題加多選題一共30道左右覆蓋數(shù)據(jù)結(jié)構(gòu)、操作系統(tǒng)、網(wǎng)絡(luò)、C/Java基礎(chǔ)簡答題兩到三道考察方案設(shè)計類的題目比如斷點續(xù)傳的實現(xiàn)思路編程題兩道難度中等偏上一道偏算法一道偏設(shè)計。整場限時在120到150分鐘之間。我當(dāng)時的策略是選擇題控制在60分鐘內(nèi)簡答題25分鐘編程題留足50分鐘最后剩一點時間檢查。這個節(jié)奏看起來簡單但實際操作中很多人栽在選擇填空上糾結(jié)太久導(dǎo)致編程題沒有時間寫。1.3 與純算法筆試的核心差異同樣是客戶端方向字節(jié)和騰訊的筆試可能更偏動態(tài)規(guī)劃、DFS/BFS這類標(biāo)準(zhǔn)算法題而迅雷的題目里明顯帶著業(yè)務(wù)痕跡。舉個例子同樣是考分塊這個概念純算法題會問一個數(shù)組分成K份求最小最大值迅雷可能就會包裝成一個文件分片后并行下載如何設(shè)計調(diào)度策略。這個差異給備考帶來的啟示是刷題當(dāng)然要刷但不能只刷題。你需要刻意訓(xùn)練自己把算法題還原成業(yè)務(wù)場景的能力或者反過來看到下載、緩存、并發(fā)這些關(guān)鍵詞時能快速想到底層的數(shù)據(jù)結(jié)構(gòu)和算法。2. 數(shù)據(jù)結(jié)構(gòu)和算法題不是最難的但一定是最能拉分的這部分是選擇題和編程題的公共基礎(chǔ)。從通過率來看算法題反而是拉開差距的關(guān)鍵。原因很簡單網(wǎng)絡(luò)和并發(fā)題大家多少能說幾句但算法題會就是會不會就是不會編不出來。2.1 選擇題里的數(shù)據(jù)結(jié)構(gòu)高頻考點就迅雷這張卷子來說以下幾塊幾乎是每年必考棧與隊列的對比、單調(diào)棧的典型應(yīng)用場景比如下一個更大元素這個知識點在選擇題里經(jīng)常和括號匹配、表達(dá)式求值混在一起考。二叉樹的三種遍歷順序、已知前序中序求后序這種題只要畫圖推一遍就不會錯。哈希表的沖突處理方式拉鏈法和開放定址法的優(yōu)缺點。各類排序算法的時間復(fù)雜度、穩(wěn)定性對比以及什么時候用快排、什么時候用堆排。這些東西看著基礎(chǔ)但線上筆試有個特點你沒法用編譯器驗證心里如果模糊就只能蒙。所以我建議備考時把每個數(shù)據(jù)結(jié)構(gòu)的操作復(fù)雜度表、應(yīng)用場景整理成一張速記表考前十分鐘掃一遍。2.2 迅雷偏愛的算法題風(fēng)格合并、分塊、Top K如果給迅雷筆試算法題貼標(biāo)簽我的答案是兩個詞分治和合并。這兩個詞非常貼合下載場景——一個大文件拆分多個分片下載下載完再合并多個任務(wù)并發(fā)執(zhí)行結(jié)果匯集排序。所以你在刷題時會發(fā)現(xiàn)合并兩個有序鏈表合并K個有序數(shù)組尋找Top K大元素這類題目出現(xiàn)頻率極高。另一個高頻方向是字符串處理和鏈表的邊界操作。字符串的題目通常不會太難但非常考細(xì)節(jié)比如去除空格、反轉(zhuǎn)單詞順序這類。鏈表的題目則偏愛反轉(zhuǎn)、環(huán)檢測、刪除倒數(shù)第N個節(jié)點這些題難度不大關(guān)鍵是在筆試環(huán)境下不能出錯。2.3 典型例題解析合并K個有序鏈表我拿一道最典型的題來說明筆試中的解題節(jié)奏。題目描述給定K個有序鏈表每個鏈表元素都是升序排列請把它們合并成一個有序鏈表。拿到題先不要直接寫代碼先建立思路。最容易想到的方案是順序合并先合并前兩個再把結(jié)果和第三個合并以此類推。假設(shè)每個鏈表平均長度是NK個鏈表這樣做的時間復(fù)雜度是O(K^2 * N)因為每合并一次都要遍歷當(dāng)前結(jié)果鏈表。稍微好一點的方案是兩兩合并也就是歸并思路時間復(fù)雜度降到O(K * N * logK)這個方案在筆試中足夠用而且代碼復(fù)雜度不高。最優(yōu)方案是用小根堆維護K個鏈表的當(dāng)前頭節(jié)點每次取出最小值再放入該節(jié)點的后繼時間復(fù)雜度同為O(K * N * logK)但常數(shù)更小。筆試環(huán)境下我推薦直接用優(yōu)先隊列方案代碼清晰不容易出錯struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; struct cmp { bool operator()(ListNode* a, ListNode* b) { return a-val b-val; } }; ListNode* mergeKLists(vectorListNode* lists) { priority_queueListNode*, vectorListNode*, cmp pq; for (auto head : lists) { if (head) pq.push(head); } ListNode dummy(0); ListNode* tail dummy; while (!pq.empty()) { ListNode* node pq.top(); pq.pop(); tail-next node; tail node; if (node-next) pq.push(node-next); } return dummy.next; }邊界條件有三個鏈表數(shù)組為空、數(shù)組中某個鏈表為空、所有節(jié)點取完后堆為空。這三個情況在代碼里都自然覆蓋了但你在寫完代碼后一定要主動檢查一遍。2.4 典型例題解析海量數(shù)據(jù)中的Top K問題另一道迅雷風(fēng)格很濃的題是Top K。比如某個下載服務(wù)一天產(chǎn)生海量日志每行記錄一個下載任務(wù)的耗時找出耗時最長的K條記錄。這道題在筆試選擇題里會考思路在編程題里會考實現(xiàn)。核心答案是維護一個大小為K的小根堆遍歷數(shù)據(jù)時如果當(dāng)前元素比堆頂大就彈出堆頂并插入當(dāng)前元素。遍歷結(jié)束后堆里的K個元素就是答案。時間復(fù)雜度O(N * logK)空間復(fù)雜度O(K)。這里有一個非常容易錯的理解點為什么是小根堆而不是大根堆。因為我們要保留最大的K個元素最小的那個在堆頂方便隨時被更大的元素淘汰。如果你用大根堆堆頂是最大的元素新元素進來時根本無法判斷該不該淘汰堆頂。迅雷筆試?yán)镞@個知識點出現(xiàn)過不止一次而且會換包裝給你一個數(shù)據(jù)流隨時查詢當(dāng)前的中位數(shù)給你100億個整數(shù)找出出現(xiàn)頻率最高的100個。本質(zhì)上都是堆這個數(shù)據(jù)結(jié)構(gòu)在解決只關(guān)心局部極值的問題。3. 網(wǎng)絡(luò)與并發(fā)下載場景下必考的系統(tǒng)知識如果把算法題比作筆試的骨架那網(wǎng)絡(luò)和并發(fā)就是迅雷筆試的血肉。這一部分的分值占比通常能達(dá)到三成以上而且選擇題、簡答題、編程題里都會出現(xiàn)它的影子。3.1 TCP協(xié)議永遠(yuǎn)繞不開的基礎(chǔ)TCP的知識點在任何公司筆試?yán)锒际潜乜嫉谘咐椎木碜永锼目疾焐疃葧钜恍3巳挝帐帧⑺拇螕]手這種送分題還會考滑動窗口、擁塞控制、以及TIME_WAIT狀態(tài)的理解。舉個例子選擇題可能會這樣出一個客戶端主動關(guān)閉連接后進入TIME_WAIT狀態(tài)需要等待多長時間為什么需要這個狀態(tài)。答案大家都知道等待2MSL但原因需要說完整第一保證主動關(guān)閉方最后一個ACK能夠到達(dá)對方如果丟失對方會重發(fā)FIN第二讓舊連接的報文在網(wǎng)絡(luò)中自然消失避免影響新連接。這個知識點為什么迅雷愛考因為下載工具需要頻繁地創(chuàng)建和銷毀TCP連接TIME_WAIT狀態(tài)的連接數(shù)量如果過多會導(dǎo)致本地端口資源耗盡這是下載器實際開發(fā)中會真實遇到的問題。3.2 HTTP與斷點續(xù)傳一道題吃透協(xié)議頭斷點續(xù)傳是迅雷筆試簡答題的常客幾乎每年都有。它考察的是你對HTTP協(xié)議的理解深度。斷點續(xù)傳的核心是HTTP的Range頭。客戶端在請求時可以帶上Range: bytes0-1023服務(wù)端如果支持段請求會返回206 Partial Content并且在響應(yīng)頭中帶上Content-Range: bytes 0-1023/2048告訴客戶端當(dāng)前傳輸?shù)氖悄囊欢巍⑽募偞笮∈嵌嗌佟榱舜_保續(xù)傳時文件沒有被修改還需要用到ETag或Last-Modified頭。客戶端先發(fā)送一個帶If-Range的請求如果ETag匹配說明文件沒變服務(wù)端返回206如果不匹配說明文件已經(jīng)被修改服務(wù)端返回200攜帶完整文件客戶端需要丟掉已下載內(nèi)容重新開始。以HTTP基礎(chǔ)加Range頭為核心再包上校驗頭這就是一個完整的斷點續(xù)傳方案。簡答題里如果考這個答題結(jié)構(gòu)可以這樣組織先講斷點續(xù)傳要解決什么問題再講HTTP協(xié)議如何支持最后講客戶端如何記錄下載進度、如何校驗文件完整性。3.3 P2P下載與多線程分片調(diào)度如果說斷點續(xù)傳是必答題那P2P相關(guān)的題目就是迅雷的特色題。作為P2P下載技術(shù)的代表產(chǎn)品迅雷對P2P原理的考察非常自然。這里的知識點包括P2P網(wǎng)絡(luò)的節(jié)點發(fā)現(xiàn)機制、種子文件的解析、分片索引信息的交換、節(jié)點之間的數(shù)據(jù)塊傳輸。筆試通常不會考得很深但至少會出一道題讓你解釋為什么多個客戶端同時下載同一個文件時越多人下載速度越快。答案的核心是P2P網(wǎng)絡(luò)中每個下載者同時也是上傳者。客戶端A下載了文件的第1到第10個分片客戶端B就可以直接從A獲取這些分片而不必都去服務(wù)器拉取。下載者越多可用的數(shù)據(jù)來源越多整體吞吐量越高。與之關(guān)聯(lián)的還有一個高頻考點多線程分片下載。為什么要分片下載因為單TCP連接受擁塞控制影響吞吐量有限多個連接并行可以顯著提升下載速度。但分片又帶來新問題分片大小怎么確定、怎么記錄每個分片的狀態(tài)、分片下載完成后如何校驗拼接、某個分片下載失敗是否需要重試。這就是一個完整的任務(wù)調(diào)度系統(tǒng)。3.4 簡答題實戰(zhàn)設(shè)計一個支持?jǐn)帱c續(xù)傳的下載器我把這類題的答題模板整理一下筆試時可以直接套先交代背景下載任務(wù)包含文件元數(shù)據(jù)、分片列表、下載進度。然后說明分片策略將文件按照固定大小如1MB切分為多個分片每個分片獨立下載。接著講記錄機制本地維護一個下載狀態(tài)文件記錄已下載分片的信息包括分片序號、偏移量、長度、校驗值。再講網(wǎng)絡(luò)請求使用HTTP Range頭請求指定分片校驗通過后標(biāo)記為完成。最后講異常恢復(fù)下載中斷后重新啟動時讀取狀態(tài)文件跳過已完成和校驗通過的下載任務(wù)只對未完成的分片發(fā)起請求。這個模板把是什么、怎么做、怎么恢復(fù)串起來了邏輯完整即使不要求寫代碼也能拿到大部分分。4. 客戶端專項內(nèi)存、線程調(diào)度和渲染的實戰(zhàn)考點迅雷的客戶端覆蓋多個平臺筆試專項部分會考查C和Java兩套體系。如果你投的是Windows客戶端方向C知識是重頭如果投的是Android/iOS方向平臺相關(guān)的知識占比會更高。但無論哪個平臺下面這幾類題幾乎是公共的。4.1 內(nèi)存管理從C智能指針到Android泄漏C方向的第一高頻考點是智能指針。unique_ptr獨占所有權(quán)shared_ptr共享所有權(quán)并用引用計數(shù)控制生命周期weak_ptr用于打破循環(huán)引用。選擇題經(jīng)常給出一個多線程場景問shared_ptr是否線程安全。答案是不完全安全引用計數(shù)本身是原子操作但指向的對象是否線程安全需要你自己保證。Java/Android方向則愛考內(nèi)存泄漏場景最常見的四個靜態(tài)變量持有Activity引用、內(nèi)部類隱式持有外部類引用、Handler延遲消息持有Activity、資源未關(guān)閉。筆試如果讓你分析一個內(nèi)存泄漏問題以及如何排查你要說出工具鏈Android Studio的Memory Profiler或者用LeakCanary自動檢測然后根據(jù)引用鏈定位到具體持有者。4.2 多線程與任務(wù)調(diào)度鎖、等待隊列和生產(chǎn)者消費者下載器是一個典型的生產(chǎn)者消費者模型。一個或多個線程負(fù)責(zé)從網(wǎng)絡(luò)拉取數(shù)據(jù)放入內(nèi)存緩沖區(qū)另一些線程負(fù)責(zé)把緩沖區(qū)的數(shù)據(jù)寫入磁盤。筆試選擇題里這個模型對應(yīng)的問題包括緩沖區(qū)用什么數(shù)據(jù)結(jié)構(gòu)、如何保證線程安全、緩沖區(qū)滿了怎么辦、緩沖區(qū)空了怎么辦。答案通常是基于鎖和條件變量實現(xiàn)互斥鎖保護共享緩沖區(qū)兩個條件變量分別表示緩沖區(qū)不為滿和緩沖區(qū)不為空生產(chǎn)者等待不滿條件消費者等待不空條件。如果你用C寫直接用std::condition_variable配合std::mutex代碼很簡潔。我會在編程題部分再展開一次這里先記住一個核心結(jié)論凡是考察并發(fā)最終都要落到一個可運行的、無死鎖、無忙等待的實現(xiàn)上。4.3 UI渲染與卡頓優(yōu)化客戶端才有的考點這一塊在通用后臺崗筆試?yán)锿耆粫霈F(xiàn)但客戶端崗幾乎必考。核心問題是為什么界面會卡頓如何優(yōu)化。標(biāo)準(zhǔn)答案是UI線程每秒需要完成60幀的渲染每幀的預(yù)算約16.6毫秒。如果主線程上有耗時的磁盤IO、網(wǎng)絡(luò)請求或復(fù)雜布局就會超過預(yù)算導(dǎo)致丟幀、卡頓。優(yōu)化方向包括耗時操作放子線程、布局層級扁平化、使用視圖復(fù)用、圖片按需加載、減少過度繪制。迅雷的下載界面有進度條、速度曲線、任務(wù)列表這些高頻刷新場景對UI性能要求不低所以這個考點非常有業(yè)務(wù)相關(guān)性。備課時建議把16.6毫秒主線程不執(zhí)行耗時操作寫在筆記本第一行。4.4 緩存與持久化從LRU到磁盤策略客戶端經(jīng)常需要緩存數(shù)據(jù)緩存相關(guān)的題目里L(fēng)RU是大熱門。LRU全稱Least Recently Used核心思想是淘汰最久沒被訪問的數(shù)據(jù)。筆試會考兩種形式一種是選擇題讓你選LRU的底層數(shù)據(jù)結(jié)構(gòu)另一種是編程題讓你實現(xiàn)一個LRU Cache。最經(jīng)典的解法是哈希表加雙向鏈表哈希表保證O(1)查找雙向鏈表保證O(1)插入和刪除。后面編程題部分我再給出完整代碼。磁盤緩存策略的簡答題也不少見焦點問題是下載了一半的文件要不要寫入磁盤什么時候?qū)懭搿W顑?yōu)策略是數(shù)據(jù)先寫入頁緩存達(dá)到一定閾值后批量刷新到磁盤避免頻繁小IO同時定期調(diào)用fsync確保數(shù)據(jù)落盤。筆試不需要寫得非常底層把批量寫、延遲寫、定期落盤這三個核心策略講清楚就夠了。5. 兩道典型編程題從讀題到AC的完整推演編程題是所有在線筆試的壓軸大題分值高、時間緊最容易心態(tài)崩。這里我拿兩道非常貼近迅雷考點的典型題完整演示一遍從讀題到AC的思考鏈路你可以在筆試時照著這個流程執(zhí)行。5.1 第一類編程題模擬分片下載的完成率統(tǒng)計題目大意是某下載任務(wù)把一個文件分成N個分片每個分片有唯一編號。現(xiàn)在給出一個日志文件里面是無數(shù)條分片編號-狀態(tài)開始/完成的記錄請統(tǒng)計當(dāng)前任務(wù)的整體完成率并且輸出所有已完成且順序正確的分片區(qū)間。拿到題先不要急著寫先定義清楚輸入輸出輸入第一行是分片總數(shù)N接下來若干行是日志記錄最后讀到一個結(jié)束標(biāo)記。輸出格式需要你計算完成百分比并輸出已完成分片的最大連續(xù)區(qū)間。核心解法思路用一個布爾數(shù)組標(biāo)記每個分片是否完成遍歷日志更新數(shù)組最后一次循環(huán)統(tǒng)計連續(xù)完成的區(qū)間同時計算完成數(shù)除以總數(shù)得到百分比。這個方案時間復(fù)雜度和空間復(fù)雜度都是O(N)完全夠用。筆試寫代碼時的關(guān)鍵點是要把輸入循環(huán)寫對。用C的while (cin a b)來讀取日志遇到EOF就結(jié)束然后在循環(huán)里做狀態(tài)更新。這個過程中最容易漏掉的是一個分片可能被多次標(biāo)記開始但完成只能生效一次以及輸入的編號是0-based還是1-based一定要按題目要求來。5.2 第二類編程題實現(xiàn)一個LRU Cache這道題在客戶端崗筆試中出現(xiàn)頻率非常高因為它同時考察了哈希表、鏈表、以及最近使用策略的業(yè)務(wù)理解。題目通常這樣描述設(shè)計一個LRU緩存支持get(key)和put(key, value)兩個操作get在key不存在時返回-1put在緩存滿時淘汰最久未使用的key。分析思路要分三步走。第一步確定需要什么數(shù)據(jù)結(jié)構(gòu)get需要O(1)所以必須有哈希表put需要O(1)插入刪除同時要維護訪問順序所以要用雙向鏈表。第二步設(shè)計哈希表的value存鏈表節(jié)點的指針這樣才能在O(1)時間內(nèi)把節(jié)點移動到鏈表頭部。第三步把接口理清楚訪問某個key時先在哈希表拿到節(jié)點然后把它摘下來放到鏈表頭部插入新key時先判斷容量是否已滿滿了就刪除鏈表尾部節(jié)點并刪除哈希表對應(yīng)項。代碼實現(xiàn)如下class LRUCache { private: struct Node { int key, value; Node* prev; Node* next; Node(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {} }; unordered_mapint, Node* cache; Node* head; Node* tail; int capacity; int size; void addToHead(Node* node) { node-next head-next; node-prev head; head-next-prev node; head-next node; } void removeNode(Node* node) { node-prev-next node-next; node-next-prev node-prev; } void moveToHead(Node* node) { removeNode(node); addToHead(node); } public: LRUCache(int capacity) : capacity(capacity), size(0) { head new Node(0, 0); tail new Node(0, 0); head-next tail; tail-prev head; } int get(int key) { if (!cache.count(key)) return -1; Node* node cache[key]; moveToHead(node); return node-value; } void put(int key, int value) { if (cache.count(key)) { Node* node cache[key]; node-value value; moveToHead(node); return; } Node* newNode new Node(key, value); cache[key] newNode; addToHead(newNode); size; if (size capacity) { Node* removed tail-prev; removeNode(removed); cache.erase(removed-key); delete removed; size--; } } };這段代碼里最容易被忽略的邊界是兩個哨兵節(jié)點head和tail。有了它們鏈表在為空時也能保證操作統(tǒng)一不用處理大量空指針判斷。筆試環(huán)境下我強烈建議所有雙向鏈表都加上哨兵節(jié)點。另一個易錯點是put已經(jīng)存在的key時一定要先更新value再移動到頭部不能先移動再更新否則節(jié)點順序會亂。最后刪除節(jié)點時要同時從哈希表erase并delete節(jié)點避免內(nèi)存泄漏。5.3 在線筆試的評測機制與自測方法很多線上筆試平臺不會告訴你為什么沒通過某個測試用例只會告訴你過了百分之多少。所以提交前一定要自己測試邊界情況空輸入、只有一個元素、滿容量時重復(fù)put、get不存在的key、連續(xù)get同一個key。還有兩個很關(guān)鍵的紀(jì)律第一不要向標(biāo)準(zhǔn)輸出打印任何調(diào)試信息評測系統(tǒng)只認(rèn)你該輸出的結(jié)果多一個字符都算WA。第二如果題目要求多組測試用例一定要用循環(huán)讀取到EOF而不是只處理一組數(shù)據(jù)。很多同學(xué)算法本身寫對了卻因為輸入循環(huán)寫錯而只拿到部分分?jǐn)?shù)。6. 考場實戰(zhàn)時間分配、環(huán)境檢查和心態(tài)控制筆試不只是考你會不會還考你在限時環(huán)境里能不能穩(wěn)定輸出。這個話題學(xué)校不教但實戰(zhàn)里非常關(guān)鍵。6.1 提前把線上筆試環(huán)境踩熟2018年的在線筆試平臺已經(jīng)比較成熟但你還是應(yīng)該提前兩天模擬一次打開平臺、切到自己要用的編程語言、復(fù)制粘貼一段測試代碼跑通編譯。千萬別等到開考了才發(fā)現(xiàn)編譯器版本太低不支持C11的某些特性或者本地IDE能用但平臺不認(rèn)。另外平臺有一些隱藏規(guī)則要提前搞清楚編程題允許使用哪些語言不同語言對輸入輸出的處理模板是什么是否支持從本地粘貼代碼。如果不確定寧可多花五分鐘在正式考試前測試環(huán)境。6.2 三個時間節(jié)點守住兩條線我把筆試時間分為三條線時間進度線、得分進度線、心態(tài)防線。時間進度線是試卷開考30分鐘選擇題必須完成一半以上60分鐘時選擇題和簡答題必須全部結(jié)束開始進入編程題。得分進度線是選擇題不確定的題先標(biāo)記不要消耗大量時間編程題優(yōu)先選擇思路最清晰的題做即使算法不是最優(yōu)也要先寫一版能過基礎(chǔ)用例的解法。什么叫先拿基礎(chǔ)分就是如果一道編程題最優(yōu)解法是動態(tài)規(guī)劃但你一下子想不出來可以先寫遞歸暴力版本通過部分用例拿到分再回頭優(yōu)化。筆試的OJ通常按通過的測試用例數(shù)給分暴力解法往往能拿三成到五成的分?jǐn)?shù)比空著強得多。6.3 有取舍地做選擇題多選寧可少選在線筆試的選擇題里多選題的計分規(guī)則通常是少選得部分分多選不得分。所以多選題沒有十足把握的選項就不要選這就是寧可少選不可錯選原則。單選則要優(yōu)先排除明顯錯誤的選項再在剩下的里面選。另一個容易踩的坑是有些題是每題多少分答錯扣分這和普通考試不一樣。答題前先看清題目說明如果答錯有倒扣那不確定的題不要隨便蒙留空反而更安全。6.4 筆試結(jié)束后的復(fù)盤動作筆試結(jié)束后不要馬上松懈趁記憶還熱立刻把剛才不確定的題目記下來。我的習(xí)慣是用手機備忘錄列出選擇題不確定的知識點清單比如TCP的某個狀態(tài)、哈希表的某個沖突處理方式。筆試結(jié)束的當(dāng)晚對照這個清單翻書補漏。補漏的意義在于校招筆試往往不止一輪同一家公司的筆試和面試知識點高度重疊你這次不確定的很可能就是面試官下一輪要問的。把筆試當(dāng)成一次免費的知識點掃描你會少走很多彎路。最后再分享一個我后來才意識到的小技巧筆試前一周與其繼續(xù)刷難題不如把斷點續(xù)傳、TCP狀態(tài)、LRU、多線程下載這幾個主題各寫一遍完整的知識框架每個主題用300字寫清核心原理應(yīng)用場景可能被問到的細(xì)節(jié)。我當(dāng)年寫了厚厚一沓筆試時遇到相關(guān)題目手速和判斷力明顯不一樣。這套方法到今天依然適用推薦給每一個準(zhǔn)備客戶端方向校招筆試的同學(xué)。