百度2011研发笔试题复盘:基础、算法与系统考点全解析
百度2011研发工程师笔试卷我最近重新翻出来做了一遍。说实话十多年前的老题放到今天看依然有相当的参考价值甚至有不少题放在现在的校招笔试里也完全不违和。百度的研发笔试向来以“基础扎实、算法硬核、不偏不怪”著称这套2011年的卷子更是把这几个特点体现得淋漓尽致。我自己在重新刷这套题的时候明显感觉到它和现在很多公司的笔试风格有差异。现在的笔试动辄就是系统设计、开放论述当年这套卷子更看重的是你对数据结构、算法、操作系统、C基本功的掌握程度题目设计得很干净没有奇怪的陷阱但你想全做对也没那么容易。把它拿出来复盘一遍对准备校招笔试、社招面试前的基础复习都有很直接的帮助。1. 这套卷子的整体难度判断与考点分布先说一个总体的感受这套试卷的难度设计比较有梯度不是上来就给你一记闷棍而是从基础题开始逐步加深到最后几道编程题才真正拉开差距。这种设计思路其实很科学它一方面保证大部分考生不至于交白卷另一方面又能在最后一小时里筛出真正有算法功底的人。从考点分布来看可以大致分成这么几块数据结构与算法部分集中在链表、二叉树、字符串处理、排序与查找、动态规划这几个方向。其中链表和二叉树相关题目几乎每年必考2011年也没有例外。语言基础部分C相关的题目占了相当大的比重涉及指针、内存管理、const、引用、虚函数、构造函数析构函数这些内容而且题目往往不是直接问你概念而是给你一段代码让你判断输出结果或者指出错误。操作系统部分进程与线程的区别、死锁、内存管理、文件系统这些经典考点都有涉及。值得注意的是它比较偏爱考“概念对比”类型的选择题比如进程和线程的对比、静态链接和动态链接的对比。计算机网络部分TCP三次握手和四次挥手、TCP与UDP的对比、IP地址分类、HTTP协议相关基础难度不高但覆盖面宽。逻辑与智力题部分这部分比较有意思虽然是研发岗但试卷里还是放了几道逻辑推理题和数学题考察的是思维的严谨性。我个人的判断是这套卷子如果你能在两个小时内完成并保证80%以上的正确率那你的计算机基础已经相当扎实了。如果只能勉强做完选择题、编程题只能写出一两道那就需要在数据结构和算法上再下功夫。1.1 为什么老题还能拿来练手很多人会质疑2011年的笔试题都过去这么多年了技术更新迭代这么快做这种老题还有什么意义其实这里有件事需要想清楚技术栈确实在变但计算机核心基础知识几乎没有变。你到今天写代码还是离不开数组、链表、哈希表、二叉树操作系统依然是那个进程线程模型TCP/IP协议栈的结构也依然稳定。百度这套卷子考的就是这些底层的东西。换句话说它测的不是你用过什么新框架而是你有没有建立扎实的底层知识体系。这个体系一旦建立学什么新东西都快因为新框架本质上都是这些基础知识的排列组合。反过来如果底层地基不牢就算简历上写满了Spring Cloud、微服务、Kafka一问到底层原理照样露馅。所以我在复盘这套卷子的时候给自己的定位不是“考古”而是“夯实基础”。每做一道题我都会问自己这个知识点在今天的工作中还有没有用用在哪里这么一问会发现几乎每道题背后的知识点都在日常开发中扮演着重要角色。2. 选择题里的“必考陷阱”与避坑方法这套卷子的选择题覆盖面很广但如果你仔细分析会发现在这些看似零散的知识点背后有几个反复出现的“必考陷阱”可以说是出题人的执念。把这些陷阱提前搞清楚比盲目刷一百道题都管用。2.1 C类相关的陷阱拷贝构造与默认构造函数C部分几乎是每套研发笔试题的重头戏2011年这套也不例外。它特别喜欢考的一个点就是类的默认行为问你一个空类里编译器会自动生成哪些函数或者某个代码片段里拷贝构造函数被调用了多少次。关于“一个空类编译器自动生成哪些函数”这个问题标准答案是四个默认构造函数、析构函数、拷贝构造函数、赋值操作符。但这个答案在C11之后需要注意一点如果类里声明了移动构造函数或移动赋值操作符那拷贝相关函数的生成规则会有变化。2011年的题目当然不会考C11的内容但你放到今天重新做这道题的时候应该主动把新标准带来的影响也梳理一遍这样才能让旧题产生新知。还有一个高频陷阱是“拷贝构造函数被调用几次”的问题这类题的经典场景是这样的一个函数按值传参、按值返回中间还涉及临时对象。我记得当年很多人在这道题上栽跟头核心原因是分不清“拷贝构造”和“赋值”的区别。只要把握住一个原则——新对象被创建的时候调用的才是拷贝构造函数给已有对象赋新值调用的是赋值操作符——就不容易被绕进去。2.2 const与指针组合的读法const和指针的组合就是const出现在星号左边还是右边的问题看起来简单但每次笔试面试都会有人搞混。2011年这套卷子直接在选择题里考了这个而且不是单纯问含义而是给了好几行代码让你判断哪一行编译报错。我自己的记忆方法是“读法记忆法”把声明从右往左读。const char *p从右往左读是“pointer to char const”也就是指向常量字符的指针p本身可以改*p不能改。char * const p从右往左读是“const pointer to char”意思是p这个指针本身是常量不能改指向但指向的字符可以改。这个方法真正用起来很顺比死记“const在星号左边修饰指向的内容在右边修饰指针本身”要直观得多。这类题能反映出一个人的代码习惯。如果你写代码时经常随意组合const和指针对各种写法的含义模棱两可那在笔试中丢分就不奇怪了。真正规范的代码里const的使用是有明确语义的能加const的地方尽量加这既是自文档化也能防止误修改。2.3 操作系统里的进程线程对比2011年这套卷子里有一道很典型的对比题进程和线程的区别。这种题看起来简单但出题人会从不同角度反复考察比如问“哪些数据是线程共享的、哪些是独立的”或者“进程切换和线程切换哪个代价更大、为什么”。关于线程共享的部分常规答案是同一个进程内的线程共享地址空间包括代码段、数据段、堆、打开的文件描述符、信号处理器等。独立的包括线程ID、栈、寄存器上下文、程序计数器、errno等。这里有一个很多人容易忽略的点栈虽然是线程各自独立的但“堆空间是共享的”意思是所有线程都能访问堆上的任何地址这既是方便也是风险。正因为堆共享多线程编程才需要加锁正因为栈独立递归局部变量才是线程安全的。放到今天这个话题还可以延伸一下协程和线程的对比。协程是用户态的、轻量级的调度单位同一线程内的协程切换完全在用户态完成不需要陷入内核所以切换代价远小于线程。这个延伸正好可以把老题和新知识串起来让你对“并发模型”有完整的认识。2.4 网络部分的TCP握手与状态迁移网络部分的必考题自然是TCP的三次握手和四次挥手不过2011年这套卷子的出法比较细它不是简单地让你列出三次握手的过程而是把SYN、ACK、FIN、TIME_WAIT这些状态混在一起让你判断在某个状态下收到某个报文之后应该进入什么状态。这就要求你不仅知道一共有哪些状态还得能把状态迁移过程完整地画出来。关于TIME_WAIT这个状态笔试里高频出现的问题是主动关闭连接的一方在收到对方的FIN之后为什么还要等2MSL答案的核心是两个原因一是确保最后一个ACK能到达对方如果ACK丢了还能重传二是保证网络中属于本连接的所有报文都消失避免影响后续使用相同端口的新连接。这个知识点放到实际工作中也非常重要。你会看到高并发的服务端经常出现大量TIME_WAIT连接如果处理不当端口被占满就会导致新的连接无法建立。当年笔试考的是状态迁移实际工作中考的是你懂不懂怎么调net.ipv4.tcp_tw_reuse、tcp_tw_recycle这些参数以及为什么tcp_tw_recycle后来被很多人认为不应该随便开。从笔试题到生产环境这条链路特别完整。3. 经典算法题复盘从暴力到最优解的实现路径这套卷子的算法题部分可以说是全卷最核心的拉分项。我当时做的时候感受很深的是出题人并不会要求你直接写出惊为天人的最优解而是很看重你对基础算法的掌握程度。换句话说只要你能把主流的思路写出来并正确分析复杂度就已经能击败相当多的人了。3.1 字符串相关的题目移动窗口、原地操作与字符统计字符串题目在笔试题里出现频率很高因为它既能考察基础编码能力又容易引申出复杂的算法思想而且不需要复杂的输入输出格式就能出题。我当时复盘时最感慨的是字符串题目里很多经典套路在十几年后的今天依然是面试高频题型几乎原封不动地延续着。第一个代表性题目是“找出字符串中最长不重复子串”。这个题目有多种解法最原始的是暴力枚举所有子串并判断是否有重复字符时间复杂度O(n^3)不建议使用用它做保底虽然能运行但分数很难看。进一步的方法是滑动窗口用两个指针维护当前不含重复字符的区间再用一个数组或哈希表记录字符最后出现的位置。每次右指针向右移动时如果发现新字符在窗口内已存在就把左指针跳到那个重复字符上一次出现位置的下一个下标然后更新答案。这样一次遍历就能完成时间复杂度O(n)。这类题目在今天的笔试题里同样常见只是外壳会换一换可能是“找出最长无重复字符的子串”也可能是“找出所有字母异位词”anagram的起始位置。2011年出的是最原始的无重复子串但核心思路——滑动窗口配合哈希表记录位置至今不变。我建议你把滑动窗口的几类经典变体都练透固定窗口、可变窗口、计数窗口。第二个代表性题目是字符串去重或字符统计。2011年这套题里出现过类似“给定一个字符串删除其中重复的字符”的变体。最简单直观的方法是使用一个布尔数组记录每个字符是否出现过然后遍历字符串只保留没出现过的字符。这个解法的时间复杂度O(n)空间复杂度O(1)因为字符集是有限的比如ASCII是256个Unicode BMP也只需要一个几十KB的位图就够了。如果你在面试中遇到同样的问题还可以主动延展说明如果字符集不确定用哈希表则空间为O(k)k为字符种类数。第三个值得反复练的是字符串旋转。给定一个字符串例如“abcdef”将前两个字符移到末尾得到“cdefab”要求原地操作不能用额外空间。这个题最经典的解法是“三步反转法”先把前两个字符反转得到“bacdef”再把剩余部分反转得到“bafedc”最后整体反转得到“cdefab”。三步反转法的本质是利用反转操作的特性通过三次反转实现一个“平移”的效果。2011年这套题在选择题里就出现过类似思路后来在面试中我也多次遇到这个题目因为它能很好地考察“如何用简单操作组合出复杂效果”的思维。字符串这块我特别想强调一下不要以为会了“库函数”就够了。笔试中经常明确要求不能使用额外空间或者要求手写实现strcpy、strcat、strcmp这类基础函数。像strcpy要“从后往前拷贝”还是“从前往后拷贝”的问题其实要区分源和目标区域是否重叠这也是面试官喜欢追问的点。建议你把这些基础函数挨个实现一遍速度会快很多。3.2 链表题目快慢指针、反转与环的检测链表相关题目在笔试中属于“不写就亏”的类型因为代码量不大套路固定效果却很好。2011年这套卷子涉及的链表题主要有几个方向单链表的反转、两个链表的第一个公共节点、链表是否有环、链表中倒数第k个节点。单链表反转是这组里面的基础题但写法值得仔细斟酌。迭代法需要有三个指针pre、cur、next每次先保存cur-next再反转cur指向pre然后pre和cur同步后移。边界条件是当cur为空时停止循环此时pre就是新的头节点。递归写法更简洁但容易在面试时紧张出错我的建议是两种都要能默写因为有些面试官要求用递归实现一遍检查你理解是否透彻。快慢指针检测链表是否有环也是这套题里常客。它的原理你应该很熟悉快指针每次走两步慢指针每次走一步如果链表里有环两个指针必然在环内相遇如果没环快指针会先到达NULL。快慢指针不仅能判断是否有环还能找到环的入口在第一次相遇点把其中一个指针重新指向链表头部两个指针每次各走一步再相遇时就是环的入口。这个继续推一推还能计算环的长度。两个链表的第一个公共节点这道题经典思路是分别遍历两条链表获得长度然后让长链表先走差值步接着两个链表同步走直到相遇。也可以使用“双指针同步走”的写法即指针pa从链表A头出发走到末尾后跳到链表B头继续走指针pb同理。两个指针最终会在公共节点相遇。这个技巧在面试中还可以延展到“找两个数组的交集”“找两个区间的重合段”等问题上。链表题的核心技巧总结下来就三件事画图、再检查边界条件、特别注意空指针。面试时如果你能边画图边写代码思路会清楚很多。3.3 排序与查找快排、归并与二叉搜索树排序算法几乎是必考内容2011年这套题里尤其强调快排和归并的“过程分析”——不是让你写一个能用就行的排序而是问你在某个特定序列上第一趟划分结束后的结果是什么、归并每一轮合并了哪些元素、时间复杂度在不同场景下的表现等。这类题目考察的是你对算法过程的真正理解而不是死记硬背模板。快速排序有几个核心点必须掌握partition的两种写法Lomuto分区和Hoare分区。Lomuto好写易错少Hoare的交换次数少一点但容易写乱。快排的复杂度分析平均O(n log n)最坏O(n^2)。最坏情况出现在每次划分都极度不均匀时比如序列已经有序且我们选择第一个元素作为pivot。解决办法是随机化选pivot或三数取中。快排是原地排序但不稳定重复元素的处理相当麻烦。如果你在笔试中遇到“三色国旗”荷兰旗问题的变体记得这实际上也是在考察快速排序的partition思想。归并排序和快排不同它稳定但需要额外空间O(n)。面试中如果考归并往往会跟其他东西串起来。比如“计算逆序对的数量”这个问题朴素的解法是O(n^2)但用归并排序可以在归并的过程中顺带统计逆序对数量整体复杂度O(n log n)。这个解法非常经典2011年左右很多公司的笔试题都爱考它。二叉搜索树的考点实际上和排序紧密相关在BST中查找一个节点的时间复杂度是O(h)h为树高当树退化成单链表时最坏O(n)。因此平衡树、AVL树、红黑树这些概念都会被串出来考。2011年这套题的选择题里出现过AVL树平衡因子的计算当时很多人在“LL、RR、LR、RL四种旋转”上犯懵。我的记忆方法是只记两条规则如果插入节点在“左子树的左子树”做单右旋LL如果在“左子树的右子树”先左后右双旋转LR以此类推。AVL树实现细节虽然现在考得少了但思路还是值得理解一遍它对于理解红黑树“为什么约定黑高相等”“为什么新插入节点是红色”很有帮助。3.4 动态规划经典模型与状态设计思路动态规划在早期笔试题里属于“区分度”最高的部分会做的人能秒杀不会的人只能干瞪眼。2011年这套卷子的算法题里动态规划类题目不算多但出现过的都是经典模型今天仍然值得反复做。经典模型一最长公共子序列LCS。这道题的状态转移方程很有代表性设dp[i][j]表示第一个字符串前i个字符与第二个字符串前j个字符的最长公共子序列长度那么当str1[i-1] str2[j-1]时dp[i][j] dp[i-1][j-1] 1否则dp[i][j] max(dp[i-1][j], dp[i][j-1])。边界条件是dp[0][]和dp[][0]都为0。拿到题不是你想着“套模板”而是想清楚为什么这个状态定义成立。经典模型二01背包问题。设dp[j]表示容量为j的背包能装下的最大价值状态转移方程是dp[j] max(dp[j], dp[j - w[i]] v[i])注意内层循环必须从大到小遍历否则一个物品会被选多次。这个“从大到小”的细节不只是笔试考点在实际编码中也经常出bug值得多练几次。经典模型三最长递增子序列LIS。朴素做法的状态转移是O(n^2)优化版用贪心二分可以把复杂度降到O(n log n)。笔试中通常O(n^2)已经能得大部分分数但如果你能写出O(n log n)的版本面试官一定会在手写代码环节对你高看一眼。优化的思路是保存“各种长度下末尾元素的最小值”然后对每个新元素用二分查找它应该放到哪里。在做动态规划题时我总结了一套自己的流程后来在面试中屡试不爽第一步定义状态想清楚状态下标表示什么第二步写状态转移方程注意是否遗漏了某些转移路径第三步初始化边界值第四步确定遍历顺序判断是否可以滚动数组优化空间。这套流程在你紧张的时候特别有用能帮你稳步推进而不是白耗时在“猜解法”上。4. 编译链接、内存布局与安全性问题隐藏的系统级考点除了纯算法和数据结构这套试卷里还有一批非常典型的“系统级”考点它们表面上不显眼但涉及的知识点几乎都是今天写代码时绕不开的关键问题。这些题目主要分布在C的内存管理、编译链接过程、指针安全性等方面难度其实比普通的选择题高。4.1 内存分区栈、堆、全局区、常量区的直观理解2011年这套题在选择题里很直接地考察了“内存分哪些区哪些变量在哪里分配”。这类题本身不难但要理解到位别只背概念。一个C程序运行时的内存布局大致如下栈区由编译器自动分配和释放存放局部变量、函数参数、返回地址等。栈空间有限默认一般在1MB到8MB之间递归太深容易爆栈。堆区由程序员手动分配和释放new/delete或malloc/free空间大但没有栈那么高效且需要确保正确的分配与释放否则就会内存泄漏或用后释放。全局区/静态存储区存放全局变量和静态变量程序启动时分配程序结束时释放。常量区/代码区存放常量字符串、字面量以及编译后的机器指令通常是只读的。实践中最常见的坑是“返回局部变量的地址”。局部变量在栈上函数返回后栈帧被回收返回的指针就成了野指针。虽然编译器通常会给出警告但在笔试手写代码时没人提醒你得自己格外注意。4.2 指针、引用与内存安全从数组越界到缓冲区溢出指针是C和C的核心也是笔试最容易出错的地方。2011年这套卷子很爱考两类指针相关题目指针做函数参数能不能改变实参如果形参是int *p在函数里修改*p可以改变实参指向的内容但修改p本身比如p new int不会影响实参。要想让实参指针本身被修改得使用二级指针int **pp或指针引用int *p。很多人在这里丢分就是没想清楚“值传递”与“引用传递”的本质。数组越界和缓冲区溢出比如char buf[10]如果用strcpy拷贝一个长度为12的字符串进去就会造成栈缓冲区溢出。这种问题不止是笔试考点在真实的安全漏洞里非常常见。所以笔试里面出现这道题本质上是考察你有没有安全意识。关于内存安全现代C里有一条明确的建议不要裸用new/delete优先使用RAII的智能指针unique_ptr、shared_ptr管理资源。但笔试往往还是考原始指针因为面试官希望确认你理解底层机制而不是只会依赖工具。4.3 编译、链接与多文件编程从宏到inline再到头文件保护2011年这套卷子里还有一个常见的考点就是“预编译、编译、汇编、链接”这一条流程以及预处理指令#include、#define、#pragma once等对被编译代码的影响。关于宏和inline宏是一种文本替换所以在编译前就发生作用但不进行类型检查。inline则是在编译阶段建议编译器将函数体嵌入调用点有类型检查是更安全的选择。现在C里也可以用constexpr在编译期计算几乎是宏的现代替代品但笔试里搞清楚“宏是文本替换”这个本质依然能帮你快速判断很多题目。头文件保护有两种方法一种是#ifndef HEADER_H另一种是#pragma once。老牌编译器普遍支持#ifndef而#pragma once在大部分现代编译器下都能正常工作。日常工程实践中两者都有人用关键是不要在同一个项目里混用两种风格。编译链接这块我还想提一个笔试高频场景多个源文件之间的符号可见性与链接错误。比如某个全局变量在一个文件里定义另一个文件里用extern声明但没定义链接时就会报未定义符号如果一个符号在两个文件里重复定义就会报重复定义错误。如果能把这个层面的错误准确识别出来笔试中遇到代码编译类的题目就稳了。5. 系统设计与并发场景初探这类题当年其实已经存在很多人觉得“研发工程师笔试卷”里主要是算法题系统设计题是后来才流行起来的。但实际上2011年这套卷子里已经有了一些“倾向性”的题目它们不一定以系统设计的形式出现但会通过一些具体的场景题、问答题让你思考并发、缓存、分布式等问题。5.1 并发场景下的经典问题生产者-消费者2011年这套卷子里有一道很经典的并发题生产者-消费者模型。它考察的角度是缓冲区有界、多个生产者多个消费者、如何用信号量或锁来实现同步互斥。这道题在那个年代的笔试里是标配放到今天面试依然会出现。核心答案是三个信号量/条件变量的组合一个empty表示缓冲区空格数初始为缓冲区大小一个full表示缓冲区已填充数据数初始为0一个互斥锁mutex保护缓冲区本身的访问。生产者在放入数据前wait(empty)放入后signal(full)消费者在取出数据前wait(full)取出后signal(empty)。关键在于“先wait再lock”顺序不能反不然会产生死锁。如果你对这套模型很熟甚至可以在面试时直接引出一个问题在Java中可以用BlockingQueue直接实现在C中则可以用std::condition_variable实现。但面试官想听的不只是你“会不会用库”而是你“知不知道底层怎么做”。所以最稳妥的做法是先讲底层信号量/锁的做法再提到现代语言封装。5.2 缓存与哈希的延伸从算法题到系统设计2011年笔试里有一类题表面上考的是哈希表设计、查找效率但背后已经埋了系统设计的种子。比如“设计一个LRU缓存”这个题目今天看已经是极其常见的系统设计题但在当时的试卷中通常会拆分成更小的考点哈希表的底层实现、链表节点的移动、O(1)时间复杂度的要求。我记得这套卷子里有一道选择题涉及哈希表的冲突处理它问的是开链法和开放定址法在不同场景下的适用性。这道题本身是数据结构题但要真正回答好必须理解内存局部性开放定址法把所有元素都放在同一块连续内存里缓存命中率更高开链法需要额外分配节点缓存不友好但删除操作更简单。这个问题放在“缓存设计”的大背景里就更清晰了——为什么Java的HashMap用的是开链法为什么Redis的字典在扩容时采用渐进式rehash为什么CPU缓存对哈希表的影响如此显著。5.3 动态规划与分布式调度看似无关底层有相同逻辑还有一个很有趣的联系是动态规划题目中“状态”的概念实际上和分布式调度中“任务依赖关系”的关系比较密切。你可以把动态规划里的“子问题”理解为调度图里的任务节点“状态转移”理解为任务之间的依赖关系那么求最优解的过程就是在有向无环图中寻找一种满足所有依赖的执行方案。2011年这套卷子的算法题里虽然没有直接出现DAG相关的题目但它在“拓扑排序”和“关键路径”这个方向上是留了伏笔的。如果你把拓扑排序算法练熟那么在涉及“任务编排”“依赖调度”的系统设计面试题里你就能比竞争者多一层理解。这也是我做老题时特别享受的部分把不同题目之间的底层联系串起来形成知识网络。6. 做题节奏、时间分配与失分点控制做完2011年这套卷子之后我最大的感悟是很多时候你拿不到高分不是因为不会做某些题而是因为时间分配不当、在个别题上死磕太久、或者因为紧张丢掉了本该拿到的分。下面聊聊我在复盘时总结的做题节奏与失分点控制方法。6.1 选择题的定位快速判断不恋战选择题部分通常40-50道占了整卷不小的比重。我的策略是每道题控制在2分钟内完成超过3分钟还不会就直接跳过做完其他题再回头思考。原因是选择题的分数是按题给的一道选择题的艰难程度和它的分值并不成正比。你把解一道难题的30分钟花在检查十几道简单题上收益远高于死磕一道大题。需要特别注意的是选择题里的“基础概念题”和“代码输出题”要区分对待。基础概念题比如进程线程区别、TCP状态迁移通常靠记忆和背诵就能拿下必须在最短时间内答完代码输出题比如某段C代码运行后的输出则需要你静下心推演哪怕答案是3分钟推出来的也不要着急。这类题一旦想当然就很容易掉进出题人的坑里。6.2 编程题的时间预留至少一小时2011年这套卷子的编程题部分我认为绝不能少于60分钟。原因很简单编程题不仅考察解法更考察代码书写的规范性和完整性。你需要在纸上或代码框里手写出完整的函数可能还要考虑边界条件、内存释放、错误处理。这些都需要时间。我的规划是先把所有编程题的题意通读一遍找出哪几题是自己最有把握的优先做这些。这里的“最有把握”不是指看着眼熟而是指你已经有清晰思路、知道时间复杂度和边界条件怎么处理。做完保底题后剩下的时间再挑战困难题。最忌讳的做法是上来就做大题结果卡在一个很小的边界条件上45分钟出不来最后连简单题都没时间写。说到手写代码我再提供一个具体的检查清单每次写完代码都要按这个清单过一遍检查边界输入空字符串、空链表、数组长度为1等。检查指针/引用是否为空是否可能访问越界。检查循环终止条件是否能在有限步内结束是否可能死循环。检查是否有动态分配的内存没有释放如果环境要求手动管理。检查函数的返回值是否在所有路径上都正确。当年笔试中这些检查往往能在最后几分钟帮你捡回不少分。6.3 容易失分的非技术因素审题、手写规范、命名还有一个很有意思的失分点是非技术层面的审题不仔细。2011年这套卷子里有一道题问的是“不改变原数组的情况下怎么做”很多同学直接在原数组上排序虽然答案本身算法没问题但完全不符合题目要求直接被判零分。这种题考察的其实不是算法而是“读题能力”。手写代码的规范性也很重要。如果你在纸上写代码就不要写一些含糊的“看起来差不多”的语法。命名要清楚缩进要整齐。面试官或阅卷人看代码时第一印象就是书写是否工整逻辑是否有条理。一个变量名乱起的解法即使思路正确也容易让人怀疑你的代码能力。命名这块我想多说一句笔试时用a、b、cnt这样的短变量名可以接受但如果能写出有含义的名字比如fast、slow、pivot、maxLen整体效果会好很多。干净的命名不仅方便自己检查也方便阅卷人理解你的思路。7. 从笔试到面试的延伸思考做完这套老卷子我觉得最有价值的收获不仅仅是“会做这些题”而是可以把这些知识点和后来的面试题串联起来。很多面试官喜欢从一个基础题开始反复引申如果你每个基础点都能接得住面试自然会很顺畅。7.1 面试官如何由一道算法题展开连环追问举一个例子如果面试官让你写“单链表反转”大部分人能写出来。但高手和普通人的区别在于追问的环节。面试官可能追加提问如果节点数量很大递归反转会不会爆栈为什么能否用递归实现递归实现的函数调用栈深度跟链表长度有什么关系如果链表是循环链表反转逻辑要怎么调整能否给出一个不使用额外空间的迭代实现它的时间复杂度是多少每一个问题背后都对应着“递归调用栈”“循环链表”“常数空间复杂度”这些基础概念。笔试里考的是“会不会写”面试里考的是“熟不熟”。如果你做笔试题时不只是背答案而是能把每一步的“为什么”搞清楚那么在面试中的临场反应就会好得多。再比如“实现strcpy”面试官会追问如果源字符串和目标字符串的内存区域重叠怎么办这时候就要从“从前往后拷贝”变成“从后往前拷贝”。而这里还会引出一个关键问题怎么判断重叠通过比较指针地址范围是否相交。这个知识点在很多实际项目里都遇到过。7.2 知识网络的构建把考点织成网在复盘整套试卷后我的一个明显感受是真正拉开差距的不是某个具体题目的解而是你能否把多个考点连成网络。比如学习“内存分区”时可以联系“栈溢出”和“递归深度限制”再联系“尾递归优化”。学习“虚函数表”时可以联系“动态绑定”和“多态”再联系到“析构函数为什么应该是虚函数”。学习“进程线程”时可以联系“上下文切换成本”再联系“协程如何进一步减少切换成本”。学习“哈希表”时可以联系“哈希冲突”“扩容”再联系“缓存设计和分布式缓存分片”。当这些知识点不是孤立记忆而是形成一张网时不管笔试面试怎么变形你都能从某个熟悉的节点出发找到答案的路径。7.3 现代公司的笔试变化老题还有多少参考价值有人会问现在的互联网公司笔试题相对以前是不是变难了我的观察是难度没有简单多少但题型多样了。现在的笔试除了传统算法题还会有更多的工程题、智力题、甚至性格测试。不过算法和数据结构依然是核心只是形式更灵活更强调在实际场景中的应用。所以在准备现代笔试时不要因为“这题是2011年的”就轻视。我的建议是先把这类经典老题刷透从中提炼出题思路和考点框架再通过新的题目来更新知识库。经典老题提供的是“不变的地基”新题则是“变化的应用场景”两者缺一不可。8. 复盘后的自我检测清单最后我在复盘完这套2011年研发工程师笔试卷后整理了一份自测清单这次也给读者一份。你不需要去查答案如果这些题目你都能快速回答上来那你的功底已经很扎实如果有一些卡壳了那正好说明这就是你的复习重点。空类里编译器自动生成了哪些函数C11和C98的答案有什么差异const在指针声明中出现在不同位置分别代表什么含义能不能用const修饰引用进程和线程的切换过程有什么不同为什么线程切换通常比进程切换开销更小请手写一个strcpy并说明如何处理内存重叠的情况。单链表反转如何实现能否用递归实现如何判断一个链表是否有环如何找到环的入口最长公共子序列的状态转移方程是什么时间复杂度是多少快速排序在最坏情况下的时间复杂度是多少原因是什么LRU缓存怎么设计用什么数据结构组合可以实现O(1)的get和put三次握手的SYN、ACK分别是哪一方发送的为什么需要三次一个C程序的内存布局分几块局部变量、全局变量、静态变量、常量字符串分别在哪里如果一段代码出现“已声明但未定义”的链接错误可能的原因有哪些这份清单虽然是我从2011年的老卷子里提炼出来的但其实放到今天来检验自己的基础依然有效。我甚至建议你每隔半年自测一次看看自己是否在某些细节上不再熟练了。基础这个东西只要有一段时间不碰就会生疏。我自己在重做这套题时踩过的最大的坑是用现在的编码习惯去写当年的代码。今天的编译器和代码风格已经变了很多比如我们几乎不用裸指针管理内存但当年笔试就是在写裸指针。所以我的经验总结就是做老题时用老规矩思考用今天的理解去提炼背后的原理。你在考场上需要的是“当年的代码规范”在复盘时需要的是“今天的高度”。如果你能把这份清单里的内容都消化掉那你想拿下一份研发岗位的笔试就不会是难事了。老题存在的意义从来就不是让你背答案而是让你在刷题的过程中把那些最底层、最本质的计算机知识一遍一遍打磨得更扎实。这套2011年的百度笔试卷在我眼里就是这样的试金石。