
前幾天整理舊硬盤翻出一個命名為“筆試整理”的文件夾里面靜靜躺著一份2015年人人網研發筆試卷E的掃描版。說實話看到它的第一反應是懷念第二反應是“這玩意兒現在還有人看嗎”。但當我真的一張一張重新過完后發現一個挺有意思的事實這套卷子里的考點和出題思路放到今天依然有很強的參考價值。不是說要你去背原題而是它代表了一類典型的、以“基礎扎實度”為核心考察目標的研發筆試題。如果你正在準備校招或者工作幾年后想回來補一補底層的短板花點時間復現這套卷子的考查邏輯比盲目刷幾十道LeetCode更能幫你建立知識體系。很多人拿到一套舊卷子容易走兩個極端要么覺得過時了沒價值要么試圖在網上找答案背下來。我都不推薦。更務實的做法是把試卷當成一份“考點清單”逐題拆解它到底在考察什么然后對照自己的知識樹查漏補缺。這篇博文我也不會去逐題貼答案——網上找不到完整版而且意義不大——而是把這套卷子里的核心題型、答題思路、踩坑點整理成一套可以復用的方法論。無論你考不考人人網這套方法都能遷移到其他公司的研發筆試里。1. 為什么2015年這套題到現在還有復盤價值1.1 一套試卷的時代背景基礎能力永遠在考察名單上人人網在2015年正處于移動端轉型的關鍵期后端技術棧從傳統的LAMP架構向分布式方向演進。這份研發筆試卷E的題型構成其實非常典型地反映了那個階段互聯網公司對研發崗位的期待你不僅要會用框架還得懂底層原理你不僅要能寫業務代碼還得能處理高并發、大數據量下的性能問題。具體到試卷結構大致是五個板塊選擇題、填空題、簡答題、編程題、設計題。選擇題主要覆蓋數據結構、C/Java語法細節、操作系統、網絡協議填空題則偏重于輸出結果題比如給一段代碼讓你寫運行結果考察的是對語言機制的理解深度簡答題通常會問“進程和線程的區別”“TCP三次握手為什么是三次”這類經典問題編程題一般是兩道左右鏈表和動態規劃是常客設計題壓軸往往是一道系統設計或方案設計。這套結構放在今天依然不過時。你去看現在各大廠的筆試卷子除了多了些機器學習、云原生的題目之外底層考察邏輯幾乎沒變。原因很簡單基礎知識是面試官唯一能快速判斷你“潛力”的指標。項目經驗可以包裝但操作系統、網絡、數據結構這些硬知識問幾句就能看出真實水平。1.2 這套卷子考察的三個核心維度復盤完整份卷子我總結出它想考察的三個核心維度這三個維度也是所有研發筆試的通用標尺。第一個維度是“編碼基本功”。包括語法細節是否扎實、邊界條件是否考慮周全、代碼風格是否整潔。比如C的虛函數機制、構造析構順序、指針與引用的區別這類題目如果平時只是“用過”而沒有“思考過”很容易翻車。第二個維度是“算法思維”。不是看你會不會背題而是看你在限定時間內能否從暴力解推進到最優解并正確分析復雜度。第三個維度是“系統視野”。簡答題和設計題考察的是綜合能力比如緩存的一致性、分布式鎖的實現、數據庫索引的選擇。能答好這類題目的人通常不是靠刷題刷出來的而是真的寫過、踩過坑、總結過。所以復盤這套卷子的關鍵不在于“記住正確答案”而在于“理解考察者為什么這么問”。一旦想通了這一點你就擁有了出題人視角以后再遇到新題也不會慌。2. 算法與數據結構題拿滿分的答題節奏2.1 鏈表和樹的基礎題先從手寫邊界條件說起這套試卷的編程題里鏈表相關題目幾乎從不缺席。原因有兩點第一鏈表能考察指針操作的基本功第二鏈表的邊界條件多很容易暴露“眼高手低”的問題。我印象中這類題的經典考法是“判斷鏈表是否有環”和“反轉鏈表”。如果你覺得這兩道題簡單不妨問自己三個問題。第一個問題快慢指針判斷鏈表有環快指針每次走兩步而不是三步為什么很多人答不上來。其實核心原因是兩步可以保證慢指針進入環后在一圈內必然被快指針追上。如果走三步追上可能需要多圈雖然最終也能判斷但邊界分析就復雜得多。第二個問題反轉鏈表時如果要求不能用遞歸迭代寫法里需要幾個指針答案是三個prev、cur、next。少一個就會斷鏈。第三個問題鏈表的入環節點怎么找這涉及Floyd判圈算法的擴展——快慢指針相遇后一個指針從頭出發一個指針從相遇點出發每次各走一步再次相遇的位置就是入環點。樹的考察點則集中在遍歷上。層序遍歷看起來簡單但很多人的寫法有隱患用nullptr作為層分隔符當節點值本身就可能是空指針時容易出錯。更好的做法是每次記錄當前隊列的大小然后一次性處理完這一層這樣既不需要額外標記也不容易出錯。前序、中序、后序的遞歸版本大家都熟但要求你寫非遞歸版本時就需要用棧手動模擬這里經常有人卡住。2.2 動態規劃的套路從暴力遞歸到狀態壓縮動態規劃是每年筆試的壓軸??瓦@套卷子也不例外。遇到動態規劃題我推薦的標準步驟是先寫暴力遞歸再改成記憶化搜索最后再優化成遞推。不要一上來就追求最優解那樣反而容易卡殼。舉個例子最長不重復子串這道題。暴力做法是枚舉所有子串檢查是否有重復字符復雜度O(n^2)。這能幫你厘清問題模型確保思路正確。接著用滑動窗口優化維護一個窗口右指針不斷右移如果遇到重復字符左指針就跳到上次出現位置的下一位。這時復雜度降為O(n)并且不需要額外數組來標記字符是否出現過只需要一個長度為128或256的數組記錄每個字符上次出現的位置。還有一個高頻考點是背包類問題。0-1背包的遞推公式大部分人能寫出來但問到“如何優化成一維數組”時容易忽略內層循環必須倒序。為什么因為一維數組滾動更新時正序會讓同一個物品被重復使用而0-1背包每個物品只能用一次所以必須倒序。這個細節也是面試官最愛挖的坑。2.3 字符串處理的邊界陷阱字符串題看似簡單實際是失分重災區。這套卷子的字符串題大多圍繞“子串”“子序列”“反轉”這幾個關鍵詞展開。比如“反轉字符串中的單詞順序”要求原地操作。很多人能寫出整體反轉再局部反轉的思路但漏掉了兩個關鍵邊界單詞間可能有多個空格輸入字符串首尾可能有空格。C里用istringstream可以省事但筆試環境不一定允許你依賴這些庫函數的語義Java里用split( )會丟掉連續空格需要手動處理。最穩妥的寫法是先去除首尾空格再用雙指針從后往前逐詞提取。再比如判斷兩個字符串是否為變位詞。常規解法是用哈希表統計字符頻率這沒錯。但如果你能用長度為26的數組代替哈希表代碼會更簡潔而且面試時能體現你對“有限字符集”的敏感度。2.4 代碼風格是隱形評分項這一點很多人會忽略筆試的編程題通常是人工閱卷代碼風格直接影響印象分。好的代碼風格包括變量命名有意義而不是a、b、c關鍵步驟有注釋函數邊界檢查充分復雜度分析寫在代碼塊旁邊或注釋里。我見過不少人代碼邏輯全對但整個函數寫的密密麻麻沒有空行變量名全部是拼音縮寫閱卷人一眼就不想看。反過來一份結構清晰、注釋得當的代碼即使有小bug閱卷人也更愿意相信你只是筆誤而不是不會。3. 語言基礎與計算機系統題送分題里的暗坑3.1 C/Java基礎容易被問倒的三個點這套卷子的選擇題里C和Java的基礎題占了很大比例但很多“感覺會”的題實測一做就錯。我挑三個最典型的點說一下。第一個是C虛函數相關的機制。題目可能會問你“構造函數是否可以聲明為虛函數”“析構函數為什么通常要聲明為虛函數”。前者是因為虛函數表在構造期間尚未完全建立調用虛函數會失去多態意義后者是因為通過基類指針刪除派生類對象時析構函數不虛就無法正確調用派生類的析構邏輯導致內存泄漏。很多有幾年經驗的人第一反應是“我平時不手動管理內存不關我事”但寫底層庫的人絕對躲不開這個問題。第二個是Java集合框架里的坑。比如HashMap的擴容機制、ConcurrentHashMap為什么線程安全、ArrayList和LinkedList的區別為什么不能只看“查詢快慢”。很多人背過結論但被問到“HashMap在JDK 1.7和1.8中擴容有什么不同”時就露餡了。1.7是頭插法并發擴容可能形成環1.8改成了尾插法并且在鏈表長度超過8且數組長度大于64時轉成紅黑樹。這些不是考題細節而是設計思想值得花時間真正理解。第三個是內存管理。C的RAII、智能指針的引用計數機制Java的GC分代回收、Minor GC和Full GC觸發的條件。這些題看起來是概念題但背后關聯著JVM調優、服務性能排查面試官問出來其實是想知道你有沒有排查線上問題的基礎。3.2 操作系統與網絡考點不能只背結論操作系統和網絡協議是筆試中“看似送分、實則暗坑”的重災區因為題目經常把結論包裝在具體場景里考。比如進程與線程的區別老生常談。但換成“一個進程內多個線程共享哪些資源、獨占哪些資源”就有很多人說不全。共享的是地址空間、文件描述符、信號處理器獨占的是棧和寄存器。再比如死鎖的四個必要條件很多人能背出來但題目給一個具體場景問你“破壞的是哪個條件”就要費一番思量了。網絡部分這道卷子幾乎必然出現TCP狀態圖。高頻題包括TIME_WAIT為什么是2MSL、為什么不能是1MSLSYN Flood的原理和防護HTTP和HTTPS的區別以及TLS握手過程。這里我建議不要死背狀態名而是把每個狀態背后的“為什么”想清楚。比如2MSL這個數字是因為要保證最后一個ACK能重傳同時確保本次連接的所有報文在網絡中消失避免干擾新連接。理解了設計意圖任何變著花樣的題都繞不開你。3.3 數據庫索引B樹是重點中的重點數據庫相關的題目在這套卷子里占了兩個方向一是SQL寫法二是索引原理。SQL寫法相對容易多表關聯、聚合函數、子查詢掌握基本語法就能應付。索引原理才是真正拉分的地方。經典問題為什么索引選用B樹而不是B樹、紅黑樹或者哈希表標準答案講三點。第一點B樹的所有數據都在葉子節點葉子節點之間用指針連接非常適合范圍查詢和排序。第二點非葉子節點不存數據每層能容納更多索引鍵讓整棵樹更矮減少磁盤I/O次數。第三點紅黑樹是二叉搜索樹樹高太高不適合磁盤存儲。哈希索引雖然單點查詢是O(1)但完全無法支持范圍查詢。除了B樹還有一個面試官愛問的細節復合索引的最左匹配原則。給一個復合索引(a, b, c)查詢條件WHERE a1 AND c3時能用到索引嗎答案是能用到a這一列但c用不到。原因是索引排序規則是先按a排再按b排再按c排跳過了b就沒有辦法用c來定位到精確區間。這類題做錯的人非常多本質原因是沒理解索引的底層排序結構。4. 設計題和開放題給面試官一條清晰的思考路徑4.1 系統設計題的回答框架這套試卷的壓軸設計題通常會給你一個具體場景比如“設計一個短URL系統”或“設計一個帶過期時間的緩存”。很多人一看到設計題就慌了因為平時只寫業務代碼沒系統想過架構。我總結了一個四步框架基本可以應對80%的設計題。第一步是需求澄清。問清楚是讀多寫多還是讀多寫少、預估QPS多少、數據量多大。筆試雖然沒法提問但你可以在回答中先定義這些假設。第二步是容量估算。根據假設算一下QPS峰值、存儲空間、帶寬需求。這里不需要精確數量級對就行。第三步是核心鏈路設計。畫出來請求從客戶端到服務端經過哪些組件每個組件的職責是什么。第四步是容錯與擴展。如果某個組件掛了怎么保證可用性如果需要擴容怎么做水平擴展。拿短URL系統舉例。核心問題有兩個長URL轉短URL的算法以及短URL跳轉長URL的存儲方案。常見算法是發號器思想——用自增ID或雪花算法生成唯一ID再轉成62進制字符串作為短URL后綴。也有人用哈希算法比如MD5取前8位但存在碰撞風險需要加鹽或布隆過濾器輔助。存儲層通常用KV數據庫Redis作為緩存加速MySQL做持久化。這些思路不需要你寫完整代碼但整個鏈路必須清晰。4.2 場景落地帶過期時間的本地緩存如果是設計一個本地緩存需要注意的點就更細了。首先是數據結構的選擇。可以用HashMap加雙向鏈表實現LRU淘汰也可以直接用LinkedHashMap但要注意線程安全性。筆試時如果要求手寫通常不強制考慮并發但如果能提到“加鎖粒度、分段鎖、讀寫鎖”等優化方案會加分不少。關于過期時間經典實現是懶刪除加定期清理的組合。懶刪除就是在get的時候檢查過期時間過期了返回null并刪除定期清理則是啟動一個后臺線程周期性掃描一部分key刪除過期數據。這兩種方式結合既保證了讀操作的實時性又能及時釋放內存。4.3 設計題中的“溝通感”筆試怎么體現設計題和編程題不同沒有唯一的“標準答案”閱卷人更看重的是你的思考路徑是否完整。所以答題時不要把每個細節都鋪開寫而是要有主次先給結論再給理由最后補充邊界情況。比如“緩存和數據庫怎么保證一致性”這種問題很多人知道要“先更新數據庫再刪緩存”但不知道為什么要這樣。如果面試官追問“刪緩存失敗怎么辦”你能提到消息隊列重試或者訂閱binlog異步刪除緩存就說明你是真思考過而不是背了面經。這種追問式的細節才是閱卷人打高分的依據。5. 考后復盤一套卷子怎么榨出三套的價值5.1 錯題歸因而不是對答案做完一套模擬卷最忌諱的就是簡單對完答案就翻篇。我給自己定的規矩是每道錯題必須寫出歸因——是“知識沒學”還是“學了沒記住”還是“記住但不會用”三種歸因對應的補救措施完全不同。知識沒學就去補基礎找教材對應章節重新過一遍。學了沒記住說明缺少系統整理需要做知識點卡片或思維導圖。記住但不會用這是最遺憾的說明刷題量不夠對題型不敏感。你需要做的是找類似知識點的不同考法反復練習到形成條件反射。5.2 建立考點-題目映射表復盤這套2015年的試卷時我做了一個很簡單卻很高效的表格推薦給你。表格有三列考點名稱、考察方式、我的掌握程度。比如“鏈表有環判斷”這一行考察方式是“快慢指針時間復雜度O(n)”掌握程度是“熟練”。再比如“B樹索引最左匹配”這一行考察方式是“寫SQL判斷是否走索引”掌握程度是“會用但說不清原理”。這張表做完之后你的薄弱點一目了然。每天花20分鐘只看“掌握程度為不熟練”的行堅持一周提升非常明顯。5.3 復盤后的模擬訓練限時、手寫、不查資料復盤完之后一定要安排一次限時模擬。環境盡量貼近真實筆試白紙手寫不進IDE不查函數文檔完全靠記憶。時間分配上選擇和填空控制在30分鐘內簡答和設計題40分鐘編程題50分鐘最后留一點時間檢查。手寫這件事真的很重要。在IDE里你可能靠補全提示和編譯器報錯發現代碼問題但筆試題沒有這個條件。手寫一遍能逼你重新記憶類名、方法簽名和語法細節。我當年手寫ArrayList的擴容邏輯時才發現自己居然記不清數組拷貝時System.arraycopy的參數順序這種細節在IDE里永遠不會暴露但考場上一定會失分。最后再分享一個小技巧。復盤一套題不要只看自己錯在哪還要想想這道題是怎么被設計出來的。既然問了“為什么快指針走兩步”面試官就能從你的回答里聽出你是“用過算法”還是“理解算法”。帶著出題人視角去學習從“背答案的人”變成“懂原理的人”這才是這套老試卷能帶給你的最大增量。