说实话,看到“人人网2015研发笔试卷C”这个标题时,我第一反应不是题本身,而是那几年海投简历的日子:宣讲会、霸面、纸质试卷、从答题卡到代码白纸的切换。人人网当时作为老牌社交平台,研发岗笔试分A/B/C卷,C卷更像是给“下午场”或“不同考点”准备的平行卷,题目不完全一样,但风格和考察面基本对齐。这篇文章就是我对C卷的一次完整复盘——它考了什么、哪些题值得展开讲、哪些陷阱我当时踩了现在才知道。如果你也在准备校招,或者想看看2015年前后互联网公司研发笔试的水准线,这份复盘应该能给你一些实打实的参照。
当然,我拿到的只是C卷的一个记忆版本,题目细节可能有出入,但我更想讲的不是“背原题”,而是考卷背后那套通用的筛选逻辑:基础、边界、时间管理,以及你在有限时间里怎么把自己的水平最大化地写出来。
1. 卷面全局:C卷的题型排布与考察意图
1.1 从题型结构看这场笔试的门道
C卷整体分为四到五个模块:选择题、填空题、简答题、逻辑/设计题、最后两道编程题。先说选择题和填空题,这部分占的分值通常在30%上下,面很广:数据结构、算法、操作系统、计算机网络、数据库,偶尔还有一两道C语言语法题。选择题考的是“你知不知道”,填空题考的是“你能不能算出来”,比如时间复杂度的推演、某段循环的输出结果、二叉树节点数计算,这类题看着简单,但特别容易在细节上翻车。
简答题一般两三道,集中在操作系统、网络、数据库上,比如“进程和线程的区别”“死锁的必要条件”“TCP三次握手的过程”,甚至“简述B+树索引为什么适合数据库”。这类题不要求你写长篇论文,但要求你踩中关键点,少一个条件就扣分。
逻辑题和设计题是拉分项,有时以选择题形式出现,有时直接是一道简答。C卷那道“如何用一枚不均匀硬币生成等概率事件”我印象很深,因为当时真有人直接在卷子上写“把硬币换掉”。设计题则比较开放,比如“设计一个短网址服务”“设计一个分布式计数器”,这类题考察的不是标准答案,而是你有没有结构化拆解问题的习惯。
最后两道编程题分值最大,占40%左右。一道偏链表或数组操作,一道偏字符串或动态规划。整个卷面拿在手里,能感觉到出题人其实在筛选两类能力:一是计算机基础是否扎实,二是手上功夫——白板写代码是否熟练。
1.2 分值分布背后的筛选逻辑
为什么笔试非要搞这么大一盘,而不是直接考几道LeetCode?因为实习和校招的笔试更像“漏斗的第一层”:人数多、成本低、能快速过滤掉基础不扎实的人。选择题能覆盖几十个知识点,编程题能反映代码落地能力,简答题能看出你理解到哪个层面,逻辑题则试探你的思维方式。
所以你会发现,C卷里很多题并不难,但覆盖面极广。你要是只刷题不看书,选择题会一半靠懵;你要是只看书不写代码,编程题又会卡壳。这其实是在提醒准备笔试的人:数据结构和算法是核心,但操作系统、网络、数据库绝对不能裸考,尤其是大厂或老牌互联网公司,基础题出得比想象中更细。
比如选择题里有一道考“排序算法中哪些是稳定的”,当时选项给的是快排、堆排、归并、选择。很多人记得“快排不稳定”,但“堆排”和“选择排序”也都不稳定,如果只记住一句话,这道题很容易错一半。再比如“TCP连接关闭时TIME_WAIT状态出现在哪一端”,这种题就是考你有没有认真看过状态迁移图,死记硬背很难不出错。
1.3 时间分配:我的实测顺序与原因
拿到C卷时我估算了一下,整卷120分钟,题量不算爆炸,但编程题要写代码,逻辑题要推理,时间依然紧张。我的策略是:先花5分钟快速浏览全卷,标记出哪些题没有“一眼看出解法”,然后直接跳到编程题写完,再回头做选择和填空,最后处理简答和逻辑题。
有人会觉得奇怪:为什么先写编程题?因为编程题分值最高,而且需要脑子清醒。你要是先做一堆选择填空,被某道偏题卡住,等做到编程题时可能只剩20分钟,手忙脚乱之下连边界条件都顾不上。反过来,先写完代码,心里就踏实了,后面即使时间紧,也不会丢大头。
当然,这套策略也有前提:你确实能在30分钟内写完两道题。如果你习惯按顺序做题,也没必要强行跳。但有一点通用:遇到思考超过3分钟还没思路的题,先跳过,标记一下,千万别耗在一道2分的选择题上,笔试最忌讳和题目“较劲”。
2. 编程题拆解:从读题到AC,关键不是答案而是边界
2.1 链表题:判断链表是否有环并返回环入口
C卷的第一道编程题我记得很清楚:给定一个单链表,判断是否存在环,如果存在,返回环的入口节点。这道题在LeetCode上属于中等难度,但纸上手写时,很多人的问题不在“会不会快慢指针”,而在“边界处理是否完整”。
思路本身不难:快指针一次走两步,慢指针一次走一步,如果没有环,快指针会先到达末尾;如果有环,两者必然相遇。相遇后,把一个指针放回头节点,两个指针都一次走一步,再次相遇的位置就是环的入口。这个结论背后有数学推导:设链表起始到环入口的距离为a,环入口到相遇点的距离为b,相遇时慢指针走了a+b,快指针走了a+b+kL,其中L是环长,又因为快指针速度是慢指针的两倍,所以2(a+b)=a+b+kL,得到a = kL - b。这意味着从头节点走a步,和从相遇点走a步,最终都会落在环入口。
手写代码时最大的坑是“空指针”。比如链表为空,或者只有一个节点,快指针移动两步时指向NULL,你必须先判断fast != null && fast.next != null再继续循环。还有,如果你只写“findCycle”而不写“findCycleEntry”,只能算对一半。我在卷子上是把两个问题拆开写的:先判断有没有环,再单独写如何找入口,这样至少能拿步骤分。
另一个细节是空间复杂度。有些同学一上来就想用HashSet记录访问过的节点,这个解法能过,但空间复杂度是O(n),快慢指针是O(1)。笔试题目里如果没写“可以使用额外空间”,默认应当往最优解靠。我当时特意在代码旁边注释了“space O(1)”,就是为了让阅卷人知道我不是只会暴力解。
2.2 字符串DP:最长公共子序列的经典递推与空间优化
第二道编程题是字符串题:求两个字符串的最长公共子序列(LCS)长度。这个考点在2015年笔试题里属于标准题型,考的是动态规划的基本功。
递推关系很稳定:设dp[i][j]表示A[0..i-1]和B[0..j-1]的LCS长度,如果A[i-1] == B[j-1],那么dp[i][j] = dp[i-1][j-1] + 1;否则dp[i][j] = max(dp[i-1][j], dp[i][j-1])。最终答案是dp[n][m]。这个二维表的填法是从左上到右下,每一行从左到右,时间复杂度O(n*m)。
笔试时如果只要求长度,其实可以用滚动数组把空间压到O(min(n,m)),但前提是你理解了状态转移只依赖上一行和当前行。我当时直接写的二维数组,因为手写码时更不容易错,而且空间没超限。现在回头看,我会建议在卷子上先写出基础版本,再补一句“如果需要优化空间,可以改滚动数组”,展现你不仅会写,还知道怎么优化。
这道题容易忽略的是两个边界:字符串为空时结果应该是0;两个字符串完全相同时结果应该是长度本身。测试的时候千万别只跑正常用例,要自己脑补空串和全相同的场景。另外,有些变形会要求输出具体子序列,那就需要回溯dp表,这比只算长度要多写一个循环,笔试时要看清题目到底要求“求长度”还是“打印结果”。
2.3 手写代码的环境与技巧
2015年的笔试环境比现在和善一些,因为很多公司还是纸质试卷,但也有部分考点是机房在线答题。C卷我记得是在一个简单的在线编辑器里做的,没有自动补全,没有编译运行,代码写完全靠自查。这种情况下,变量名和缩进不仅仅是美观问题,更是你自己检查代码逻辑的基础。
我的习惯是先用伪代码在草稿纸上理一遍流程,再誊写到答题区。伪代码不需要规范,重点是让“思路顺序”可视化。比如链表题,我会先写三行:1. 判空 2. 快慢指针找相遇点 3. 同步走找入口。然后照着伪代码填真实代码。写完后,我会手动模拟一个简单的用例:1->2->3->4->2,从头开始一步一步走指针,看是否会死循环。这种“纸上跑测试”的方法在无法编译的环境里极其重要。
另一个小技巧是:变量的命名尽量用能表达含义的单词,比如slow、fast、p、q。不要写i、j、k到处都是,因为一旦代码长了,你自己都会忘记j是干什么的。阅卷人看到清晰的命名,也会觉得你编码习惯好,这在主观题里是隐性加分项。
3. 计算机基础题:看似送分,实则最容易丢分
3.1 操作系统:进程、线程、死锁一个都不能漏
C卷选择填空里有好几道操作系统题。最经典的是“进程和线程的区别”,几乎年年出现,C卷也不例外。但出题方式有时候不是直接问你区别,而是给几个说法让你判断正误,比如“同一进程内的线程共享地址空间”“线程是资源分配的基本单位”“进程切换比线程切换开销大”。你只有把概念理解准确了才不会被绕进去。
进程是资源分配的基本单位,线程是CPU调度的基本单位。同一进程里的多个线程共享进程的地址空间和资源,但每个线程有自己的栈和寄存器上下文。进程之间彼此独立,所以一个进程崩溃不会直接影响另一个进程;而线程共享地址空间,一个线程越界可能把整个进程搞挂。这些话必须能条件反射一样写出来。
死锁也是必考。四个必要条件是互斥、持有并等待、不可抢占、循环等待。简答题如果只写“死锁就是死等”基本没分,必须把四条件列全,并简单解释每个条件的意思。预防方法也要对应起来:破坏互斥一般不现实,但可以破坏持有并等待——比如进程在运行前一次性申请所有资源;破坏不可抢占——允许系统强制剥夺资源;破坏循环等待——资源按编号排序,申请必须按递增顺序。有些同学说“我没复习死锁”,那这场笔试基本就和操作系统说再见了。
3.2 网络:TCP握手和HTTP状态码是常客
网络题在C卷里的比重不低。选择题里有一道是“TCP建立连接过程经历了哪几次握手”,这个基本送分;但简答题有时会升级为“TCP的TIME_WAIT状态的作用是什么”。TIME_WAIT出现在主动关闭连接的那一端,作用是确保最后一个ACK能够被对方接收,如果丢失,对方会重发FIN,主动关闭方需要停留在TIME_WAIT才能再次发送ACK。同时还要留出足够时间让旧连接中的迟到报文段在网络中消失,避免影响新连接。这个问题非常经典,体现在实践里就是“高并发服务器上为什么会出现很多TIME_WAIT连接”。如果你在试卷上能把这两种原因说出来,阅卷人就知道你不是背的。
HTTP状态码也常考。选择题给一个场景,让你判断返回哪个状态码,比如“请求的资源不存在”对应404,“服务器内部错误”对应500,“资源已永久移动”对应301,“临时重定向”对应302。还有GET和POST的区别,从语义上看,GET用于获取资源,POST用于提交数据且可能改变服务端状态。这里有个容易混淆的点:POST不是“更安全”,只是不在URL里暴露数据,但在传输层一样是明文,所以不能把安全帽扣到POST头上。
3.3 数据库:索引为什么用B+树,事务的ACID怎么解释
数据库部分的题目集中在索引和事务两方面。索引题最喜欢问“为什么InnoDB用B+树而不是哈希索引或二叉搜索树”。哈希索引等值查询最快,O(1),但不支持范围查询,也不支持最左前缀匹配;二叉搜索树在数据量变大时高度太高,退化成链表后就变成O(n)。B+树是平衡多叉树,高度低,叶子节点用链表连接,天然支持范围扫描,而且所有查询都从根到叶子,路径长度稳定,更利于磁盘预读。这个知识点要是能答到这个层次,简答题基本就能拿到满分。
事务的ACID四个特性要能分开解释:原子性(Atomicity)指事务里的操作要么全做,要么全不做;一致性(Consistency)指事务完成后数据满足所有完整性约束;隔离性(Isolation)指并发事务之间互不干扰;持久性(Durability)指事务一旦提交,结果不会丢失。笔试时还会顺带问隔离级别:读未提交、读已提交、可重复读、串行化。MySQL InnoDB的默认隔离级别是可重复读,常用MVCC实现,回答时如果能提到“多版本并发控制”这个词,会比只说“锁”显得更懂行。
SQL题还可能有一道分组统计,比如“按部门统计员工数并筛选出人数大于10的部门”。这种题最容易忘H