
搜狐2017秋招研发工程师笔试试卷二这个标题乍一看像是某个应届生求职季的回忆碎片但在我眼里它更像一张浓缩的“技术体检表”。这些年我参与过不少校招笔试的出题和阅卷也带过团队面试新人回头再看这份试卷它的考察点、难度曲线和出题逻辑其实很有代表性既有对基础功底的硬核考察也有对工程思维的隐性试探。这篇文章我就以这份试卷为切入点结合当时互联网公司研发岗招聘的普遍情况拆解一下它到底在考什么、为什么这么考以及如果你穿越回去该怎么稳稳拿下这些分。这份试卷适用的人群很明确准备参加互联网公司研发岗校招的学生尤其是目标定在搜狐这类一线互联网企业的计算机相关专业同学。当然对已经工作几年的开发者来说拿它当一次“基础自查”也很有价值很多知识点平时写业务代码用不上但面试和晋升考核时会突然杀个回马枪。1. 试卷整体印象与考点分布先说结论这份试卷整体难度中等偏上对基础知识的覆盖非常全面而且有几道题目的出题角度相当刁钻不是死记硬背就能应付的。它不像部分公司那样大量堆砌偏题怪题而是更看重候选人是否真正吃透了计算机专业的核心课程。从考点分布来看大致可以分为几个模块操作系统、计算机网络、数据结构与算法、数据库、编程语言基础再加上一两道逻辑推理或工程场景题。这种分布基本就是当年互联网公司研发岗笔试的“标准套餐”搜狐试卷二比较有特色的地方在于它在操作系统和网络部分的题目占比明显偏高这也侧面反映了后端研发岗位对这两块知识的重视程度。我当时拿到这份试卷的第一感受是出题人很懂技术也很懂应届生的薄弱点在哪里。比如操作系统部分它不直接问你“进程和线程的区别是什么”而是拿具体的调度场景、死锁条件来考察网络部分也不考简单的TCP三次握手流程而是深入到拥塞控制的状态变迁和异常处理。这种考法对只刷面经、背答案的同学来说杀伤力极大。我花了大约一个半小时完整做了一遍这份试卷正确率大概在八成出头。做错的几道题恰恰是当年我自己面试时也含糊过的知识点。这也印证了一个规律笔试考察的深度往往不在于题目本身有多难而在于它能不能精准命中你知识体系里的盲区。2. 基础题解析操作系统与计算机网络的深度考察2.1 操作系统从进程调度到内存管理的隐形陷阱操作系统模块有几道题让我印象很深。其中一道关于进程调度算法的题目表面上看是考察先来先服务FCFS、短作业优先SJF、时间片轮转RR这些经典算法的平均周转时间计算但出题人在进程到达时间上埋了坑。如果按照教科书上的理想化模型去做很容易忽略进程到达顺序对SJF算法的影响——实际上非抢占式SJF在进程陆续到达的场景下必须先判断当前时刻有哪些进程已在就绪队列再从中选择最短作业而不是简单地拿全部进程按执行时间排序。这道题的完整计算过程是这样的假设有A、B、C三个进程到达时间分别是0、2、4服务时间分别是8、4、1。如果老老实实画甘特图会发现非抢占式SJF的执行顺序是A0到8、B8到12、C12到13平均周转时间是8109/39个单位。这里很多人会踩坑以为应该先算短作业直接从C开始但实际上C在第4个时间单位才到达A已经在运行了非抢占式调度下不能打断A的执行。这个细节恰恰是理论学习和实际应用之间的典型差距。内存管理部分有一道关于页面置换算法的题目也很有意思。题目给出了一个页面访问序列要求分别计算FIFO先进先出和LRU最近最久未使用算法的缺页次数。这道题本身不复杂但它的陷阱在于题目指定了页框数量为3访问序列里频繁出现对某个页的循环访问。如果你手算LRU时没注意维护“最近使用”的准确顺序很容易在第三四次访问时搞错淘汰对象。我建议遇到这类题直接在草稿纸上画一个三行多列的表格每一列代表一次访问后页框的状态这样逻辑清晰不容易出错。还有一道关于死锁的判断题考察的是死锁产生的四个必要条件互斥、占有且等待、不可抢占、循环等待。题目给出四个场景让考生判断哪些可能产生死锁。其中一个场景是“多个进程竞争同一类资源且每个进程在申请新资源前必须释放已占有的资源”这个场景其实破坏了“占有且等待”条件因此不会死锁。很多同学一看“竞争资源”就条件反射地选会死锁这就是对概念理解不够透彻的表现。2.2 计算机网络TCP协议细节与状态机变迁网络部分的题目难度比操作系统模块还要再上一个台阶。有一道关于TCP拥塞控制的题目考察的是慢启动、拥塞避免、快速重传和快速恢复这几个阶段的阈值变化。题目设定了一个场景某个TCP连接的初始拥塞窗口为1个MSSssthresh初始值为16当拥塞窗口增长到24时发生丢包问丢包后拥塞窗口变为多少、ssthresh变为多少。很多人想当然地认为发生丢包后拥塞窗口会直接降为1重新进入慢启动。这话对了一半但漏掉了快速重传机制的存在。如果发送方收到三个重复ACK它不会把拥塞窗口降到1而是执行快速恢复算法ssthresh设置为发生丢包时拥塞窗口的一半24/212拥塞窗口暂设为12然后进入拥塞避免阶段线性增长。这道题的价值就在于提醒我们TCP的可靠传输不是一个简单的“丢包就重传”模型而是一套精细的拥塞控制工程体系。关于HTTP状态码试卷里也出现了一道容易被忽略的题考察301和302的区别。出题人给了一个场景说某个网站将页面A永久迁移到页面B服务器应该返回什么状态码答案是301因为永久性迁移应该让搜索引擎更新索引同时浏览器端也会缓存这个重定向。如果是临时的活动页面跳转才应该用302。这道题在当年的面试里几乎成了“必问”但在笔试里结合场景出现反而更能筛选出真正理解HTTP语义的候选人。这里我想单独说一下网络部分的复习策略。我见过太多同学把TCP的三次握手、四次挥手背得滚瓜烂熟但一遇到拥塞控制的快速恢复、TCP状态变迁图中的TIME_WAIT与CLOSE_WAIT区别就开始含糊。实际上大厂笔试更爱考的恰恰是这些“你以为你懂但实际讲不清楚”的细节。提示TIME_WAIT状态为什么需要等待2MSL因为要确保最后一个ACK能被对端收到同时防止旧连接的报文段在新连接中重复出现。这个知识点几乎每年笔试都会出现值得重点掌握。3. 数据结构与算法手写代码与分析能力并重3.1 一道考察链表操作的编程题数据结构与算法部分是笔试的大头分数占比通常最高。这份试卷里有一道关于单链表操作的编程题要求在不使用额外存储空间的条件下判断一个链表是否存在环并找出环的入口节点。这道题有两种经典解法。第一种是哈希表法遍历链表把每个节点的地址存入哈希表如果某个节点的地址已经在哈希表中说明存在环这个节点就是环的入口。这种方法简单易懂但空间复杂度是O(n)不符合题目的“不使用额外存储空间”限制。第二种是Floyd判圈算法也叫快慢指针法。具体操作是定义两个指针slow和fast都从链表头出发slow每次走一步fast每次走两步。如果链表无环fast会先到达链表尾部如果有环两个指针必然在环内相遇。相遇后把slow移回链表头然后slow和fast都改为每次走一步继续前进两者再次相遇的位置就是环的入口。很多讲解只告诉你怎么做却没解释为什么第二次相遇时位置就是环的入口。我在这里推导一下假设链表头到环入口的距离为a环入口到第一次相遇点的距离为b相遇点继续前进到环入口的距离为c那么环的周长为bc。第一次相遇时slow走过的距离是abfast走过的距离是abkbc其中k是fast在环内绕的圈数。由于fast速度是slow的两倍有2ababkbc化简得a kbc- b (k-1)(bc)c。这意味着从链表头到环入口的距离a等于从第一次相遇点继续走c的距离再加上整数倍的环周长。所以当slow移回链表头后两个指针以相同速度前进必然会在环入口相遇。这个推导过程就是这类题目的“魂”理解了它你才算真正掌握而不是背代码模板。这道题的代码实现也很简洁struct ListNode *detectCycle(struct ListNode *head) { struct ListNode *slow head, *fast head; while (fast fast-next) { slow slow-next; fast fast-next-next; if (slow fast) { slow head; while (slow ! fast) { slow slow-next; fast fast-next; } return slow; } } return NULL; }我在阅卷时见过不少同学在这道题上栽跟头。最常见的错误是把题目要求的“找出环的入口”做成了“判断是否存在环”只写了两指针相遇就返回白白丢了后半部分的分数。第二个常见错误是遍历链表的循环条件写错没有判断fast-next是否为空导致空指针访问。这种细节在笔试环境下特别容易出错建议平时练习时就养成“先判断边界再写核心逻辑”的习惯。3.2 二叉树的遍历序列还原另一道算法题考察的是二叉树知识。题目给出了某二叉树的前序遍历序列和中序遍历序列要求还原这棵二叉树并写出它的后序遍历序列。这道题考察的知识点其实很单一前序遍历的第一个节点一定是根节点中序遍历中根节点的位置把左右子树分隔开。只要抓住这个核心思路递归地去切分序列就可以了。关键在于模拟这个过程时要细心。首先从先序序列中取出第一个元素作为根节点然后在中序序列中找到这个元素的位置它左边就是左子树的中序序列右边就是右子树的中序序列接着根据左子树和右子树的长度把先序序列的剩余部分也切成两段最后递归地对左右子树执行同样的操作直到序列为空。我建议大家在平时练习时不光要会做这道题还可以顺手实现一个由后序序列和中序序列还原二叉树的程序。原理是相通的后序序列的最后一个节点是根节点。这样举一反三一次练习覆盖两种题型。这里还有一个实操技巧还原出来的二叉树可以再用它的后序遍历结果去验证是否与原题一致这样能有效自查错误。3.3 动态规划经典背包问题的巧妙变形这份试卷的最后一道算法题是一个动态规划的问题背景换成了“背包问题”的一个变种。题目大意是有一组物品每个物品有重量和价值背包的容量有限问在不超重的前提下最多能装多少价值的物品。这就是经典的0/1背包问题状态转移方程是dp[i][j] max(dp[i-1][j], dp[i-1][j-weights[i]] values[i])其中dp[i][j]表示前i个物品在容量为j的背包中能获得的最大价值。但出题人加了点料物品总数达到1000背包容量也达到10000如果开1001×10001的二维数组1亿个int需要约400MB内存这在笔试环境里很可能直接内存超限。所以这道题真正考察的是空间优化能力——用滚动数组把二维dp压缩成一维dp遍历时从后往前更新。for (int i 1; i n; i) { for (int j capacity; j weights[i]; j--) { dp[j] max(dp[j], dp[j - weights[i]] values[i]); } }这里有一个新手很容易理解的误区为什么内层循环必须从后往前因为一维数组状态下如果从前往后更新那么dp[j - weights[i]]在计算dp[j]时可能已经被本轮更新过等于同一件物品被重复放入背包这就变成完全背包问题了。而从后往前遍历dp[j - weights[i]]还保留着上一轮的状态才能保证每件物品最多选一次。这个细节是我在指导新人时反复强调的重点。4. 数据库与编程语言容易被忽视的送分题4.1 数据库索引与SQL语句优化数据库部分的题目相比算法题要温和不少但同样存在一些容易混淆的考点。有一道题给出了一个用户表其中包含字段id主键、username、age、created_at问在WHERE条件为“age 25 AND username abc”时应该如何建立联合索引才能最优。很多人不假思索地选择直接在username上建索引理由是username查等值。但实际最优方案是建立username, age的联合索引或者至少在username上建立索引。原因在于当查询条件同时包含等值匹配和范围匹配时把等值匹配的字段放在联合索引的最左侧才能充分利用索引的有序性来加速范围查询。如果只在age上建索引MySQL在执行时虽然能用上索引做范围扫描但仍需要回表去读取username字段效率明显降低。这个知识点在笔试和实际业务中都非常实用。关于SQL语句有一道题是写一条SQL查询“每个部门工资最高的员工”。这题用窗口函数可以优雅地解决但在2017年那会儿MySQL 8.0还没普及很多同学还在用GROUP BY MAX的写法。我建议手写两种方案一种用子查询一种用窗口函数如果需要支持MySQL 8.0。SELECT department_id, employee_name, salary FROM ( SELECT department_id, employee_name, salary, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn FROM employees ) t WHERE rn 1;这类题目真正考察的是对SQL分组聚合语义的理解以及对窗口函数的熟练度。窗口函数现在已经成为大厂面试的基础要求但在校招笔试里仍然属于“加分项”。如果你准备校招不管目标公司用什么数据库都建议把窗口函数四个经典用法ROW_NUMBER、RANK、DENSE_RANK、LAG/LEAD练熟。4.2 C和Java的语法细节考察编程语言基础部分这份试卷给C和Java各出了一道题。C那道题考察的是虚函数和纯虚函数的区别以及基类析构函数为什么要声明为virtual。这个考点非常经典背后隐藏着一个实际的工程问题如果用基类指针指向派生类对象然后delete这个指针而基类析构函数不是虚函数那么派生类的析构函数就不会被调用导致资源泄漏。这就是C中虚析构函数的价值所在。Java那道题则是考察字符串比较。题目定义了两个字符串变量一个用字面量赋值String a abc;另一个用new关键字创建String b new String(abc);然后问“a b”和“a.equals(b)”分别返回什么结果。凡是学过Java的人都知道比较的是引用地址equals比较的是内容。但题目还追问了一句如果把b变成intern()的返回值再比较a b结果如何答案是true因为intern()方法会把字符串内容放入常量池如果常量池中已有相同内容的字符串就直接返回其引用。这道题的考察点很常见但结合了常量池机制之后又能筛掉一批不求甚解的候选人。5. 逻辑推理与工程场景题考察综合素质的分水岭试卷末尾通常有一两道逻辑推理或工程场景题用来考察候选人的综合分析和方案设计能力。这份试卷里有一道典型的系统设计简答题设计一个短网址服务要求支持高并发访问并说明存储方案和重定向流程。这道题没有标准答案考察的是候选人在面对一个真实工程问题时的思考框架。我阅卷时总结了一个得分点模型生成短码的方式可以用发号器如Redis INCR或数据库自增ID再用Base62编码也可以用随机数去重表。前者简单可控后者无需全局发号但存在碰撞风险。存储方案短码到原网址的映射可以存在MySQL同时用Redis做缓存缓解数据库压力。重定向流程客户端访问短网址 - 后端查缓存 - 命中则返回302到原网址 - 未命中则查数据库 - 回填缓存 - 返回重定向。高并发优化网关层做限流缓存层做LRU淘汰数据库层做读写分离。过期策略定期清理或惰性删除具体取决于业务需求。这道题的重点不是方案有多么完美而是你能不能结构化地展开思路。哪怕答案里有些瑕疵但只要能体现出“访问流程 — 存储设计 — 并发策略”这条逻辑主线就能得到大部分分数。反之如果只写一句话“用Redis存一下就行”哪怕技术方向是对的也给不了高分。这种题的存在其实是在提醒各位大厂要的不是只会刷题的人而是能快速拆解问题、给出可落地方案的工程师。笔试虽然只是面试流程的第一环但它筛选出来的往往是那些在压力下依然能保持清晰思路的人。6. 实战复盘我重做这套试卷的踩坑记录6.1 纸上得来终觉浅动手一算才发现的问题说实话我自认为对操作系统和网络的知识掌握得还不错但做这份试卷时还是翻车了两道题。一道是上面的SJF调度计算另一道是TCP快速重传后的拥塞窗口变化。它们的共同点是知识点本身都学过但在考试限时环境下很容易因为先入为主的直觉写错答案。先说SJF那道题我的第一反应是按服务时间排序把C排在最前面得出平均周转时间最小的结论。但仔细一算发现C在t4才到达A在t0就开始执行且不可抢占所以C根本没有机会提前执行。这个问题说白了就是对“进程到达时间”这个约束条件不够敏感。教科书里的调度算法默认所有进程同时到达但实际系统里显然不是这样。出题人就是故意把这个约束条件放进题目考察你是否真正理解了算法的动态执行过程。再说TCP拥塞控制那道题。我当时直接把“发生丢包”等同于“回到慢启动”没注意到题目里并未提到超时而说收到了连续三个重复ACK。这两个丢包信号的处理方式是截然不同的。我重做之后专门把拥塞控制的状态变迁画了一遍图才彻底理清这套逻辑。6.2 时间分配与做题顺序的复盘整场笔试限时120分钟我完成所有题目大约用了85分钟。回头复盘这个节奏其实第一遍快速扫描全卷、先做有把握的题是拿分的关键。如果一开始就和某道算法题较劲很容易导致后面简单的SQL题没时间写。我的做题顺序建议是先做数据库和编程语言基础题这些题属于送分题能快速建立信心。再做数据结构和算法题计算量大但思路清晰关键是把边界条件考虑周全。最后做操作系统和网络题目因为这类题往往需要仔细辨析概念容易“一卡卡半天”。动态规划这类综合题放在算法部分内部如果短时间内没思路宁可先跳过把所有能拿的分先拿到手。这个策略不一定适用于所有人但核心原则是通用的笔试是限时竞争把有限时间投入到得分效率最高的题目上本身就是一种工程决策能力。这个能力恰恰是大厂笔试想筛选的另一层素质。6.3 校招备考方法论的几点心得做完整套试卷我最大的感受是校招笔试的题目范围是有限的每个知识点就像一个抽屉你要做的是把每个抽屉都拉开看一眼确认自己都知道里面放着什么而不是只在某个抽屉里翻来覆去找各种奇技淫巧。基于这份试卷和近几年校招题目的趋势我可以给准备笔试的同学几条具体的建议操作系统把进程管理、内存管理、文件系统三块吃透重点掌握调度算法、死锁、页面置换的经典计算题。计算机网络TCP和HTTP是绝对核心状态变迁图建议自己画三遍以上。拥塞控制的慢启动、拥塞避免、快速重传、快速恢复四个阶段之间的阈值变化必须能用手算模拟一遍。数据结构与算法链表、二叉树、堆栈、动态规划是最常见的出题方向。重点不是刷多少题而是把每类题目的核心思路吃透做到举一反三。建议用笔在纸上手写代码模拟考试环境。数据库索引原理、SQL编写尤其是分组聚合和窗口函数、事务隔离级别是出现频率最高的三类题。编程语言不如抓最常用的那一两门语言把语法细节和常见陷阱整理成自己的错题本。这套方法未必能保证你进大厂但它至少能帮你在笔试阶段稳稳过线。毕竟笔试面前人人平等比拼的就是谁的基础更扎实、谁在高压下少犯错。