
每次校招季總有人翻出前幾年的經(jīng)典筆試卷來研究。完美世界2017校招技術綜合A卷就是這樣一份被反復拿出來討論的題目原因很簡單它代表了一類游戲公司技術筆試的典型出題思路——覆蓋面廣、基礎要求扎實、算法題占比重并且?guī)黠@的C傾向。哪怕放在今天這套卷子考察的知識點依然是游戲研發(fā)崗筆試的核心理解它的出題邏輯比刷十套新題更有用。我剛入行那會兒為了準備游戲公司校招把能找到的往年筆試題都刷了一遍完美世界的這套A卷是其中質量相當高的一份。它不偏不怪但每道題都能看出出題人對基礎功底的重視。這篇文章我結合自己做過的題和后來帶新人時總結的經(jīng)驗把這份卷子涉及的核心技術點、典型題型拆開講一遍同時也聊聊這類筆試背后的考察邏輯。正在準備游戲公司校招的同學或者想系統(tǒng)復習CS基礎的開發(fā)者都可以參考。1. 從這套綜合卷看游戲公司校招筆試的出題邏輯先聊一個很多人忽略的問題為什么游戲公司的技術筆試要考綜合卷了解出題動機比糾結每道題本身更有價值。完美世界這類做大型網(wǎng)游、端游、手游研發(fā)的公司技術崗位覆蓋客戶端、服務端、引擎、工具鏈、數(shù)據(jù)庫運維等多個方向。但校招筆試階段他們通常不會按具體崗位出得特別細而是先拿一套技術綜合卷篩一遍基礎。這套卷子的核心目的是確認兩件事第一你的計算機基礎是否成體系第二在時間壓力下你能不能快速準確地解決問題。所謂綜合體現(xiàn)在題目分布上。根據(jù)我接觸過的信息和同類卷子的普遍規(guī)律這套A卷大體包含這樣幾個模塊模塊大致占比考察目標數(shù)據(jù)結構與算法35%-40%邏輯思維、編碼功底、復雜度意識C/C語言20%-25%內(nèi)存管理、指針、面向對象理解操作系統(tǒng)10%-15%進程線程、內(nèi)存調度、并發(fā)基礎計算機網(wǎng)絡10%-15%TCP/IP、Socket、網(wǎng)絡編程基礎數(shù)據(jù)庫5%-10%SQL基礎、索引原理、事務概念邏輯/智力題5%左右臨場反應、思維縝密度這個比例不是隨便定的。游戲公司無論是客戶端還是服務端日常開發(fā)都重度依賴C所以語言題占比高是必然的。而算法題占比最高是因為它最能篩選出底層思維能力強的人——這類人上手新項目、排查復雜bug通常更靠譜。有意思的是這套卷子很少出現(xiàn)偏題怪題。我印象中它考的東西都是教材里反復強調的知識點但出題角度會刻意繞開背誦題更多考理解題。比如不會直接問你什么是虛函數(shù)而是給你一段存在繼承關系的代碼讓你推導輸出結果。這種出題方式背概念的人會吃虧真正寫過代碼、調試過程序的人反而覺得簡單。2. 數(shù)據(jù)結構與算法模塊整張卷子的分水嶺數(shù)據(jù)結構與算法是這套卷子的絕對核心也是面試官最看重的部分。我見過不少筆試成績不錯的同學算法模塊就能拉開別人二三十分。這部分的題目往往不直接考會背哪種排序而是換個包裝讓你現(xiàn)場分析。2.1 鏈表與樹的經(jīng)典考察方式綜合卷里鏈表和二叉樹是高概率出現(xiàn)的考點。原因很直白游戲項目里場景管理、NPC列表、技能CD隊列、消息分發(fā)底層大量用到鏈表和樹結構。筆試不會考你刷LeetCode那種上百行的難題而是考基礎操作的變體。一種典型考法是給定一個單鏈表判斷是否有環(huán)并找出環(huán)的入口節(jié)點。這題看著簡單但考察了兩個層次第一層是快慢指針的判斷方法第二層是找入口時為什么會有一個兩倍速的推導過程。很多同學知道快慢指針能判斷環(huán)但被追問相遇點和入口節(jié)點之間的數(shù)學關系時會卡殼。我當時復習時專門把這類推導過程寫了一遍因為這個為什么比怎么做更能體現(xiàn)你懂不懂。另一種高頻考法是二叉樹遍歷的非遞歸實現(xiàn)尤其是中序遍歷。遞歸版本大部分人都能寫但要求用棧模擬非遞歸時很多人會在何時入棧、何時出棧、何時訪問節(jié)點三個狀態(tài)里繞暈。我建議準備時自己推一遍模擬過程拿一棵三層滿二叉樹用紙筆畫幾輪入棧出棧的序列把細節(jié)刻進腦子里考場上就會條件反射式地寫出來。2.2 排序與復雜度分析不是背是算排序算法幾乎是所有綜合卷的必考項。這套A卷涉及的排序題重點不在默寫代碼而在三類問題第一給一個特定數(shù)據(jù)分布比如近乎有序的數(shù)組問選擇哪種排序最優(yōu)。答案是插入排序因為近乎有序時它的比較次數(shù)接近O(n)。這題考察的是對不同排序算法特性的理解而不只是時間復雜度表。第二問快速排序在最壞情況下的時間復雜度和觸發(fā)條件。最壞情況是每次劃分都極度不均比如數(shù)組本身有序且每次選第一個元素作為基準時退化成O(n2)。我面過的人里有一半能答出O(n2)但說不清觸發(fā)條件。這其實是排序原理的理解問題。第三要求分析某個排序算法是否穩(wěn)定并說明原因。以選擇排序為例它是不穩(wěn)定的——比如{5, 3, 5, 1}里有兩個5第一輪選出1交換到第一個位置第一個5被換到后面兩個5的相對順序就變了。這種細節(jié)題很能看出有沒有真正理解排序的交換過程。復雜度分析也是重點。我建議大家復習時不要只記O(1)、O(n)、O(log n)這些記號要能說出每個量級的典型場景。比如哈希表平均O(1)但最壞O(n)二叉搜索樹平均O(log n)但退化成鏈表后變O(n)這些邊界情況正是筆試喜歡出題的角落。2.3 一道典型的綜合算法題拆解結合這類卷子的普遍規(guī)律我復原一道很能代表綜合A卷風格的題目給定兩個有序數(shù)組要求合并后保持有序并分析時間復雜度。這題常見的坑有三個第一個是用concat再排序時間復雜度變成O((nm)log(nm))雖然能過功能測試但在筆試答案里會被扣分第二個是沒考慮一個數(shù)組先遍歷完的情況導致越界第三個是原地merge時從前往后覆蓋會把原數(shù)組元素沖掉正確的做法是從后往前填。標準的歸并寫法是雙指針從前往后掃描比較兩個指針指向的元素小的放入結果數(shù)組時間復雜度O(nm)空間復雜度O(nm)。如果題目要求O(1)空間那就利用兩個數(shù)組尾部有多余空間的特性從后往前移動。這類題目想表達的核心是你不僅要寫出能跑的代碼還要寫出復雜度最優(yōu)、邊界完善的代碼。3. 操作系統(tǒng)與網(wǎng)絡游戲研發(fā)躲不開的底層功課很多準備校招的同學覺得操作系統(tǒng)和網(wǎng)絡離游戲客戶端開發(fā)很遠這其實是個誤解。客戶端要處理多線程渲染、資源異步加載、網(wǎng)絡同步服務端要處理高并發(fā)連接、內(nèi)存管理、I/O模型——每一個都和操作系統(tǒng)、網(wǎng)絡的底層機制強相關。所以這套卷子在OS和網(wǎng)絡上的出題不會浮于表面通常會結合具體場景。3.1 進程與線程絕不只考概念關于進程和線程筆試題目常見的出法有幾種線程和進程的優(yōu)缺點對比、進程間通信方式列舉、死鎖產(chǎn)生的四個必要條件。但完美世界這類公司的卷子往往還會加一道場景題。我印象里比較典型的一道是多個線程同時對一個全局變量做自增操作最終結果一定等于線程數(shù)嗎答案是不一定因為自增操作不是原子的它包含讀取、加一、寫回三步多個線程交錯執(zhí)行時會出現(xiàn)丟失更新。進一步追問就是如何解決加鎖互斥鎖、原子操作C11的std::atomic、或者無鎖編程。這題考察的是對并發(fā)本質的理解而不是靠背概念能答好的。死鎖那四個條件——互斥、占有并等待、不可搶占、循環(huán)等待——也是必背。但筆試常考的是變體給一段加鎖代碼判斷是否可能死鎖或者問怎么打破死鎖條件。我記得當年復習時自己總結過一句解決死鎖最實用的思路是讓加鎖順序全局一致比如兩個線程都按先鎖A后鎖B的順序取鎖循環(huán)等待就被破壞了。這句話在筆試論述題里很好用。3.2 TCP三次握手與Socket編程的常見考點網(wǎng)絡模塊里TCP三次握手和四次揮手幾乎是必考。但程度好的卷子不會只讓你默寫過程而是會問為什么握手要三次揮手卻要四次答案的關鍵在于握手時雙方都能主動發(fā)起連接所以第三次握手是為了確認客戶端的接收能力正常防止已失效的連接請求突然到達服務端造成資源浪費而揮手時服務端可能還有數(shù)據(jù)要發(fā)所以ACK和FIN分開發(fā)送就多了一次。Socket編程題也經(jīng)常出現(xiàn)常見的考法是給一段代碼讓你指出錯誤或說明流程。比如客戶端connect失敗后立即close服務端在accept之前客戶端就發(fā)數(shù)據(jù)這些問題都考察了對TCP狀態(tài)機的理解。我建議復習時把listen、accept、connect、read、write、close這個流程在紙上默寫一遍同時標出每步的系統(tǒng)調用可能返回的錯誤碼比單純看書有效得多。另外OSI七層模型和TCP/IP四層模型的對應關系也是基礎題但通常不會單獨出而是綁定在具體問題里。比如問HTTP和HTTPS各自工作在哪一層HTTPS在TCP和HTTP之間加了SSL/TLS層目的是加密和身份認證。這種題簡單但能看出你是否有完整的網(wǎng)絡分層意識。3.3 內(nèi)存管理游戲客戶端最敏感的話題游戲開發(fā)對內(nèi)存是出了名的斤斤計較??蛻舳艘环葙Y源的加載、卸載、緩存策略服務端每個玩家對象的內(nèi)存占用都直接影響性能。所以這套卷子里內(nèi)存管理相關題目也有一席之地。虛擬內(nèi)存、分頁、分段這些概念屬于基礎??嫉氖翘摂M內(nèi)存解決了什么問題——讓多個進程能隔離運行同時允許進程使用超過物理內(nèi)存大小的地址空間。而堆和棧的區(qū)別幾乎每年都考考察點包括分配方式棧自動分配、堆手動分配、分配方向棧向低地址增長、堆向高地址增長、效率差異棧高、堆低和存儲內(nèi)容棧存局部變量和函數(shù)調用信息堆存動態(tài)分配的對象。C相關的內(nèi)存考察還會深入到內(nèi)存泄漏——程序運行過程中不再使用的內(nèi)存沒有被正確釋放。筆試常考的形式是給一段代碼找出內(nèi)存泄漏的位置或者問用哪些工具可以檢測valgrind、AddressSanitizer等。這里我多說一句面試官問這些其實是想知道你有沒有真正的調試經(jīng)驗因為內(nèi)存泄漏這種問題光看不練是發(fā)現(xiàn)不了的。4. C/C語言與數(shù)據(jù)庫游戲公司最看重的基本功游戲公司技術崗C是繞不開的主語言。無論是客戶端引擎還是服務端邏輯絕大部分代碼都是C寫的。所以這套綜合卷里C相關題目占的比重相當大而且考得非常細。數(shù)據(jù)庫模塊占比雖然不大但往往會有一道服務端玩家數(shù)據(jù)存儲相關的SQL題。4.1 指針、引用與內(nèi)存經(jīng)典中的經(jīng)典指針和引用的區(qū)別是C筆試的常青樹這套卷子也不例外。兩者的核心區(qū)別包括引用必須初始化且不能改變指向、引用沒有獨立的內(nèi)存地址從語義上講、sizeof(引用)得到的是所引用對象的大小而sizeof(指針)在64位系統(tǒng)下是8字節(jié)、指針可以有多級但引用不行。這些點不復雜但很多人被問到時容易漏項建議用表格把區(qū)別寫全記憶。智能指針也是高頻考點。C11引入的unique_ptr、shared_ptr、weak_ptr分別解決什么問題、各自的使用場景是什么這是校招生必須清楚的。我見過一道很典型的題多個shared_ptr循環(huán)引用會導致什么答案是引用計數(shù)無法歸零造成內(nèi)存泄漏。解決方法是把其中一個改為weak_ptr打破循環(huán)。這道題在游戲項目的對象管理里有非常實際的應用場景——比如場景對象和其子對象互相持有對方的shared_ptr時。const關鍵字的各種用法也是??柬?。const修飾變量、指針const int* p和int* const p的區(qū)別、成員函數(shù)const成員函數(shù)承諾不修改對象狀態(tài)、函數(shù)參數(shù)const引用避免拷貝基本都是送分點但如果平時沒在意很容易在指向const的指針和const指針之間繞暈。4.2 面向對象三大特性的筆試出題方式面向對象——封裝、繼承、多態(tài)——在C筆試里很少直接問什么是多態(tài)而是通過代碼題考。比如給一段包含基類、派生類和虛函數(shù)的代碼問輸出結果是什么如果析構函數(shù)沒有加virtual會發(fā)生什么。這里有個經(jīng)典考點基類析構函數(shù)不聲明為virtual通過基類指針delete派生類對象時只會調用基類的析構函數(shù)派生類的析構函數(shù)不會執(zhí)行導致派生類中動態(tài)分配的資源泄漏。正確答案是把基類析構函數(shù)聲明為virtual。為什么因為虛析構讓delete動作能沿著虛函數(shù)表找到最底層的派生類析構函數(shù)實現(xiàn)從外到內(nèi)的完整析構。字節(jié)對齊、構造函數(shù)和析構函數(shù)的執(zhí)行順序先基類后成員再自身也是常出的題。純虛函數(shù)和抽象類也是高頻考點。抽象類不能實例化純虛函數(shù)是接口約定由派生類實現(xiàn)。游戲開發(fā)里的組件系統(tǒng)、狀態(tài)機、尋路算法經(jīng)常用抽象基類定義接口再派生出不同實現(xiàn)。筆試考這個本質上是看你對面向接口編程有沒有概念而不只是記語法。4.3 數(shù)據(jù)庫SQL與索引基礎數(shù)據(jù)庫題在這套卷子里題目量不大但屬于送分題級別丟了可惜。常見考點包括SQL基本增刪改查、多表連接inner join、left join的區(qū)別、GROUP BY配合聚合函數(shù)的使用以及索引的基本原理。有個特別經(jīng)典的考察點是給一張表問在哪些字段上建索引能提升查詢效率為什么。答案不是全建索引就完事——因為索引會降低寫入速度、占用存儲空間。正確思路是在WHERE條件、JOIN關聯(lián)字段、ORDER BY排序字段上建索引在高區(qū)分度字段比如玩家ID上的索引效果優(yōu)于低區(qū)分度字段比如性別。同時使用函數(shù)包裹字段、隱式類型轉換、LIKE前置通配符都會讓索引失效這些都是在游戲日志查詢中經(jīng)常遇到的坑。事務的ACID特性、隔離級別、臟讀/不可重復讀/幻讀的區(qū)別也偶爾出現(xiàn)。這些概念對服務端處理玩家并發(fā)操作非常關鍵——一個交易系統(tǒng)如果事務隔離級別沒選對就可能出現(xiàn)玩家同時買一件物品導致超賣的問題。5. 筆試實戰(zhàn)中的通用策略拿到題目后怎么思考這部分我想聊聊比具體知識點更重要的東西——考場上面對一套綜合卷你的解題順序和思考策略。我當年參加筆試時吃過虧后來幫別人做模擬面試也發(fā)現(xiàn)不少同學不是不會而是策略出了問題導致會做的題也丟了分。5.1 時間分配先拿穩(wěn)定分再啃硬骨頭技術綜合卷的題量通常不小時間卻只有兩小時左右。我建議拿到卷子先花兩分鐘快速瀏覽一遍所有題目做一次難度分級第一檔穩(wěn)定分概念題、基礎知識選擇題。比如sizeof大小、SQL查詢、簡單復雜度判斷。這類題要先做確保拿滿。第二檔重點分中等難度算法題、代碼閱讀題。這類題需要思考和一定時間放在中間做。第三檔拔高分綜合算法大題、開放性設計題。放到最后時間不夠時至少寫出思路框架能拿過程分。一個常見誤區(qū)是死磕某道卡殼的算法題做完一道再說。筆試不是競賽過線需要的是總分的穩(wěn)定而不是單題的完美。我見過不少同學在快排的非遞歸實現(xiàn)上耗了二十分鐘結果后面簡單的SQL題和網(wǎng)絡題沒時間做非常不劃算。5.2 代碼題的答題規(guī)范思路比結果更重要手寫代碼題閱卷老師通常有兩種評分方式一種對照測試用例看結果另一種按步驟看思路。很多公司的筆試題其實兼有兩者——代碼能跑通最好跑不通的話思路清晰也能拿到大部分分。所以寫代碼題時我會這樣組織答案先在旁邊用一兩句話寫明算法思路用什么數(shù)據(jù)結構、關鍵步驟是什么再寫代碼代碼里注釋重點行的作用。比如寫歸并排序時先寫遞歸拆分有序合并時間復雜度O(nlogn)再開始寫代碼。這樣就算個別邊界條件寫錯閱卷人也能看出你懂這個算法。還有一點變量命名盡量語義化。筆試代碼雖然不用達到工程標準但一個叫mergeHelper的函數(shù)總比f1、f2顯得專業(yè)。這個細節(jié)在閱卷時是真的會加印象分的。5.3 心態(tài)與驗證寫完代碼一定要走一遍示例筆試時最常見的丟分原因是代碼寫完不驗證。建議寫完一道算法題立刻用一個最簡單的輸入在腦子里跑一遍代碼邏輯檢查邊界數(shù)組為空時怎么辦只有一個元素時怎么辦目標值不存在時返回值是什么比如寫二分查找時檢查左右指針的更新邏輯會不會死循環(huán)寫鏈表反轉時檢查頭節(jié)點為nullptr時會不會解引用空指針。這些小細節(jié)平時在自己電腦上編譯運行根本發(fā)現(xiàn)不了但筆試考的就是你在無編譯環(huán)境下的嚴謹度。平時練習時養(yǎng)成寫完就走查的習慣考場上就不會心里發(fā)虛。6. 復盤與備戰(zhàn)這套卷子教我的幾件事最后聊點實際的關于這套2017年的試卷對后來備戰(zhàn)校招的參考價值。雖然題目逐年更新但核心考察邏輯是穩(wěn)定的。我復盤下來有幾點想強調的。把基礎概念理解透比刷題數(shù)量重要。這套卷子里的題目嚴格來說沒有一道是超綱的全部來自教材里的基礎知識。但為什么有人考高分、有人掛在60分線上區(qū)別就在于對為什么的理解深度??炻羔槥槭裁茨芘协h(huán)、虛析構為什么能避免內(nèi)存泄漏、TCP為什么三次握手——這些問題的答案教科書上有但沒有深入思考過的人考場上很難自己推出來。動手寫代碼和純看書的復習效果差別巨大。我準備校招那陣子每天堅持手寫兩三道經(jīng)典算法題不依靠IDE提示寫完用編譯器驗證。堅持一個月后筆試時手寫代碼的流暢度明顯不一樣。強烈建議準備階段用紙筆寫代碼編譯器驗證的方式練習這能模擬考場環(huán)境又能得到反饋。最后想分享一個小心得不要把復習局限于單一公司的真題。完美世界這套2017年A卷的知識點覆蓋面其實是很多游戲公司、互聯(lián)網(wǎng)公司技術筆試的共性。我當年刷完這套卷子后再去參加其他公司的筆試發(fā)現(xiàn)很多題目只是換了層皮核心考點大同小異。把一套綜合卷吃透比零散地刷幾十套題效率高得多。如果你正在準備校招建議拿這套卷子做個自測卡好時間做完然后逐題復盤自己的卡殼點。那些讓你猶豫的題目就是你接下來復習的重點方向。筆試沒有捷徑但走對方向、用對方法確實可以少走很多彎路。