ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

2016搜狐研发工程师笔试题解析:从算法到操作系统的校招备考指南

2016搜狐研发工程师笔试题解析:从算法到操作系统的校招备考指南 前阵子整理旧资料的时候翻出一套搜狐2016年的研发工程师笔试题。那段时间我在给团队做校招面试题参考正好拿出来对比了一下现在的笔试题风格发现挺有意思的几年过去题型包装变了不少但底层要考的东西几乎没变。这套题在准备校招的技术圈子里一直是经典练习素材原因其实很简单——搜狐的笔试题出得很规范难度梯度比较合理既能筛掉基础不牢的人又不至于让思路正常的人完全无从下手。如果你正在准备研发岗位的笔试面试或者已经工作几年想回头补一补基本功这套题都值得当一面镜子。很多人一听到“笔试题”三个字就头大觉得无非是刷题背答案。但2016年这批题目有意思的地方在于它不完全是算法题库式的题目里面掺杂了不少操作系统、计算机网络和逻辑推理的东西。说白了笔试不是在考你会不会某道题而是在考你有没有形成一套完整的工程思维。下面我就按考察维度、典型题型、实战策略和避坑经验这几个方向把这套题拆开聊聊。1. 2016年搜狐笔试题的考察逻辑与能力模型1.1 研发笔试到底想筛什么样的人先说一个很多人搞错的前提校招笔试不是用来招“算法竞赛选手”的而是用来筛掉基础不扎实、思维混乱、动手能力差的候选人。2016年那会儿互联网公司普遍的做法是笔试筛一轮、技术面筛两三轮笔试的定位是“海选门槛”题目设计得既要保证区分度又要控制整体难度不然简历全进面试环节负责面试的工程师就得累趴下。从这套题的结构来看考察维度大致分四块数据结构和算法、操作系统基础、计算机网络基础、逻辑推理和智力题。这个结构在当年的校招笔试题里非常典型。数据结构和算法不用说这是程序员的核心基本功操作系统和网络考的其实是“你对计算机系统有没有整体认知”逻辑智力题则是在考察你把一个陌生问题转化成计算模型的能力。这四块加在一起正好对应了一个研发工程师日常工作中最常用的几种思维模式。这里有个容易被忽略的点笔试中的基础概念题往往比算法题更能反映一个人的真实水平。算法题可以靠短期刷题突击但进程和线程的区别、堆和栈的差异、TCP为什么需要三次握手这类问题如果理解不到位写出来的答案会显得很虚。面试官看笔试卷的时候重点也是在找那些“会做题但不懂原理”和“懂原理但做题粗糙”之间的平衡点。1.2 题型结构对备考方向的启示我之前帮团队整理过几个校招季的笔试试卷发现搜狐这套题在结构上有一个很典型的特点客观题占了一部分比例剩下的全是手写代码和简答。客观题考概念和细节手写题考实现能力简答题考表达和逻辑。这种结构其实是合理的因为它避免了“只会背书”和“只会写码”这两种单腿走路的情况。备考的时候针对这种结构要做的事情也很清楚。概念题部分把《深入理解计算机系统》里关于内存、进程、线程的章节吃透再把TCP/IP协议栈的基本流程理清楚基本就够了。手写代码的部分常见数据结构的增删改查、排序查找、链表二叉树相关操作必须能闭着眼睛写出来。简答题考的是你组织语言的能力别写流水账按“背景-思路-关键点”三段式来答印象分能高不少。我自己的体会是如果你能在一份笔试卷里同时看到“基础概念”和“算法实现”占据几乎相同的比重那这家公司要的就是“基础扎实的工程型选手”而不是“只会刷题的竞赛型选手”。对号入座去准备就可以了。2. 核心考点拆解从高频考点到很容易丢分的地方2.1 数据结构与算法不只是刷题要理解“为什么这么设计”数据结构和算法在笔试里占了大头这是肯定的。但2016年笔试题的风格和现在很多公司喜欢出偏题怪题不同它更看重对经典内容的掌握程度。高频考点集中在几个方面数组和链表的相关操作、栈和队列的应用、二叉树的各种遍历、排序算法的时间空间复杂度对比、二分查找的变种、动态规划入门。举个例子排序算法是每年必考的但考法有讲究。聪明的考法是让你比较快排和归并排序在时间复杂度和空间复杂度上的差异或者让你分析堆排序为什么不稳定。如果你只是背了“快排平均O(n log n)最坏O(n^2)”这种结论却说不清楚划分不均匀会导致退化也没有自己动手实现过那遇到变体题就很容易露馅。链表相关的题目也是重灾区尤其是单链表反转、判断链表是否有环、找链表中倒数第K个节点这类。这些题思路不难但手写代码时很容易栽在指针操作上。笔试的时候没有IDE帮你调试全凭脑子里模拟指针变化过程这本身就是一种能力筛选。我的建议是平时练习时不要只在IDE里写完就跑要尝试用纸笔走一遍代码逻辑特别是链表、二叉树这类指针操作比较多的题目这个习惯在笔试现场会很占便宜。还有个容易被忽视的点是复杂度分析。很多同学写得出代码但问时间复杂度就含糊。2016年的笔试题里很多算法题会要求你写出算法的时间复杂度如果你只写代码不分析复杂度会扣分。这背后其实是一个很实际的要求工程师不仅要写出能跑的代码还要能预估代码能不能撑住线上流量。所以备考的时候每做完一道题顺手写一下时间复杂度和空间复杂度养成这个习惯面试时你会感谢自己。2.2 操作系统进程、线程、内存这些概念为什么反复考操作系统这块是很多人备考时容易忽略的部分觉得“跟算法没关系”但实际笔试里出现频率极高。搜狐这套题里涉及到进程和线程的区别、死锁产生的四个必要条件、堆和栈的区别、虚拟内存和页面置换、进程间通信方式等等。这些内容听起来很“八股”但你细想就会发现它们和日常开发是直接相关的。举几个实际的场景排查线上服务响应变慢时如果不懂线程上下文切换的开销你就很难解释为什么线程数翻倍后性能反而下降做缓存淘汰时如果不理解LRU和LFU各自的适用场景就选不对方案处理内存泄漏时如果不理解栈上分配和堆上分配的区别连排查方向都可能找错。笔试考这些本质上是在确认你有没有这些底层认知。教科书式的答案好背但想真正理解这些概念我建议换个角度不要问“什么是进程”而是问“为什么操作系统要引入进程这个概念为什么有了进程还要线程”。顺着这个思路想你会发现进程是为了隔离资源线程是为了共享资源、降低切换成本。理解了这层设计意图面试官再追问“协程和线程有什么区别”时你也能从容应对了因为万变不离其宗都是在回答“如何更好地利用CPU”这个问题。2.3 计算机网络从TCP三次握手看面试官想听什么网络部分的考点非常集中基本就是TCP/UDP、三次握手、四次挥手、HTTP协议这些。搜狐这套题里考了TCP三次握手的过程和为什么需要三次握手这类问题几乎是所有公司笔试面试的标配。但很多人只背了“SYN、SYNACK、ACK”这三步却解释不了为什么是三次而不是两次。这里分享一个我当时觉得挺好用的理解方式三次握手的核心目的是让双方都确认自己和对方的收发能力都是正常的。第一次握手客户端发出SYN服务端收到了服务端知道自己接收没问题、客户端发送没问题第二次握手服务端回SYNACK客户端收到后知道自己发送没问题、接收没问题也确认服务端发送没问题、接收没问题第三次握手客户端回ACK服务端收到后确认客户端接收没问题、自己发送没问题。到这里双方才都确认了彼此的收发能力正常。如果只有两次握手服务端无法确认客户端的接收能力是否正常也没法处理自己发出的SYN超时重传可能带来的资源浪费问题。这样去理解三次握手笔试简答时就不会答得干巴巴了。把设计意图讲清楚比列出三个步骤要加分得多。类似的还有四次挥手为什么客户端要等待TIME_WAIT这些设计背后的道理才是面试官真正想听的。关于网络部分我还有个具体建议最好能自己动手抓一次包用Wireshark或者tcpdump看看TCP三次握手和四次挥手的实际过程。纸上得来终觉浅真实看到SYN、ACK的报文交互比你背十遍状态转移图都管用。而且面试时如果你能说“我实际抓包验证过”这个细节会让面试官眼前一亮。2.4 逻辑与智力题考的是建模能力不是脑筋急转弯2016年的笔试题目里还有一类让很多人头疼的题逻辑推理和智力题。比如经典的25匹马找最快的3匹、在天平上找异常球、倒水问题等等。很多同学一看到这种题就觉得是脑筋急转弯其实不然。这些题考察的是建模能力——把一个具体问题抽象成可以用算法或数学工具解决的形式。以“25匹马5个赛道找出最快3匹最少需要几次比赛”这道题为例多数人第一次做都会答错。这题的正确思路是先分成5组比5次每组排出名次然后每组第一名再比一次假设这场比赛结果出来后就可以排除掉大量不可能进入前三的马。这个过程中真正有价值的不是答案本身而是你推理时能不能把信息量用足。面试官其实想看你面对一个开放式问题时怎么分析、怎么假设、怎么把问题规模降下来。我的建议是笔试前可以适当做几道经典的逻辑推理题练练手但不需要花太多时间因为这类题通常占比不大。如果考试时遇到先冷静画个图或者列个表把条件理清楚通常不会完全没思路。反而是那些一看不会就放弃的同学丢分会比较可惜。哪怕最后没推出完整答案把分析过程写出来批卷人也会认为你有基本的逻辑素养。3. 经典题型的解题思路与代码实现3.1 手写二分查找边界条件才是真正的考点二分查找是笔试中的“钉子户”几乎年年考。这题看着简单但能一次写对的人不多因为边界条件太容易错了。我记得当年刷题时统计过第一次手写二分查找能完全通过边界测试的同学比例不到一半。先看一个最常见的写法def binary_search(nums, target): left, right 0, len(nums) - 1 while left right: mid left (right - left) // 2 if nums[mid] target: return mid elif nums[mid] target: left mid 1 else: right mid - 1 return -1这个写法里有两个关键点容易被忽略。第一mid left (right - left) // 2而不是(left right) // 2前者可以防止left和right都很大时相加溢出这个细节笔试时很多人想不到。第二while left right这个条件的边界当left和right相等时mid会指向同一个位置此时还需要判断一次因为有可能目标值正好在这个位置。如果写成left right循环会提前退出漏掉这个判断。还有一种比较难一点的变体找左边界或者右边界比如找升序数组中第一个大于等于target的位置。这种题更考验对不变量的理解写的时候建议配合一个具体数组在纸上走一遍效果比空想好很多。面试时如果让你写二分查找多半会跟着追问一个变体题平时把这些变体练熟了当场就不用慌。3.2 单链表反转递归和迭代的两种思维链表的题也是笔试常客尤其是单链表反转。这题考察的不只是你会不会写还在考察你对指针和引用操作的敏感度。这里给出迭代和递归两种实现代码不长但背后是两种不同的思维方式。class ListNode: def __init__(self, val0, nextNone): self.val val self.next next def reverse_list_iterative(head): prev None curr head while curr: next_node curr.next curr.next prev prev curr curr next_node return prev def reverse_list_recursive(head): if not head or not head.next: return head new_head reverse_list_recursive(head.next) head.next.next head head.next None return new_head迭代版的核心逻辑是保存下一个节点再翻转当前节点的指针。这里最容易犯的错是翻转了curr.next之后后面节点就找不到了所以必须在翻转之前把next_node保存下来。这种错误在笔试现场特别常见因为脑子里容易把“当前指针”和“下一个节点”混淆。递归版的写法非常简洁但理解门槛更高。它的核心逻辑是递归函数每次接收一个head如果head是最后一个节点就直接返回否则先把后部分链表反转好再让head.next.next指向head也就是把当前节点接到新链表末尾。这个“反转后串回去”的过程建议画图理解。笔试题里如果时间紧张写迭代版更稳妥递归版虽然优雅但call stack太深时会有栈溢出风险这也是面试官可能追问的点。3.3 动态规划从暴力递归到状态转移动态规划是很多人笔试时的噩梦。2016年的题目里也有经典的动态规划题基本套路是先给你一个可以用递归暴力求解的问题然后追问如何优化。以“最长公共子序列”为例很多同学能写出递归版本但一说到动态规划就卡住了。def lcs(s1, s2): m, n len(s1), len(s2) dp [[0] * (n 1) for _ in range(m 1)] for i in range(1, m 1): for j in range(1, n 1): if s1[i - 1] s2[j - 1]: dp[i][j] dp[i - 1][j - 1] 1 else: dp[i][j] max(dp[i - 1][j], dp[i][j - 1]) return dp[m][n]这里最关键的是理解dp[i][j]的含义它表示s1的前 i 个字符和s2的前 j 个字符的最长公共子序列长度。理解了状态定义状态转移方程就顺理成章了如果当前字符相等就在之前的基础上加1如果不相等就取两种“少一个字符”情况下的最大值。很多同学把动态规划写错不是不会转移方程而是状态定义没想清楚就开始填表了这是大忌。笔试遇到动态规划题我的建议是先用文字写出状态定义和转移方程再对照着写代码。别一上来就疯狂敲代码先把思路理清楚哪怕最后代码没写全思路对了也能拿不少步骤分。搜索记忆化、动态规划、滚动数组优化这些进阶技巧如果学有余力也应该掌握笔试中偶尔会有压轴题用到。3.4 手写代码时的规范与鲁棒性除了算法本身的正确性笔试阅卷时还有一个隐形评分点代码规范性和鲁棒性。我老东家技术团队在评笔试时就遇到过很多“逻辑对了但代码一塌糊涂”的情况。变量名命名不一致、缩进混乱、没有考虑空输入或者数组越界的情况这些都会影响批卷人的印象分。举几个具体的例子。第一个是函数入口处要判断空指针链表题里尤其重要很多同学写反转链表时没考虑输入是空链表的情况直接访问head.next就崩了。第二个是变量名要能见名知意不要写p1、p2、t1这种让人猜的命名。第三个是代码缩进和括号对齐要清楚手写代码没有IDE帮你格式化自己一定要注意可读性。我见过不少同学笔试通过率很高但代码风格一直被面试官吐槽后来才意识到这个问题的严重性。实际上笔试代码的规范性加在一起可能有5到10分的差距在竞争激烈的校招中这几分可能决定能不能进入面试环节。所以备考时就要养成好习惯每次写题都当成面试现场来写该判空的判空该写注释的地方写注释别想着“笔试而已没人看细节”。4. 现场笔试的答题策略与节奏控制4.1 时间分配先拿保底分再冲难题笔试的时间通常比较紧张搜狐这类公司一般会给60到120分钟要完成选择题、简答题和编程题时间并不宽裕。很多同学容易陷入“死磕一道题”的陷阱花40分钟做一道20分的题导致后面30分的题完全没时间看这是非常亏的。我自己的策略是“两步走”进考场先花3到5分钟快速浏览全卷给每道题标注一个大致的难度和分值然后先把有把握、分值高的题做掉主要目标是确保拿到80%的保底分把选择题、简单代码题稳定拿住再来啃动态规划或智力题这类有难度的。如果前面有题卡住超过10分钟果断先标记出来去做后面的题最后有时间再回来补。这个策略听起来简单但实际操作中很多人做不到因为心理上不愿意放弃一道已经投入了时间的题。这里有个比较实用的心理暗示你来笔试的目标是“总分过线”不是“每道题都做对”只要保证过线就行战略性放弃某道难题是完全正常的。尤其是那种分值不高但特别耗时的智力题放在最后做更合适。4.2 不会做也要留下思考痕迹笔试和面试一样面试官希望看到你的思考过程而不仅仅是结果。这道题你会做满分不会做也别空着把能想到的思路写出来。哪怕是“我想到可以用递归但边界条件还没理清”这种程度的过程也比白卷强得多。批卷人在看卷的时候其实是在寻找“这个候选人有没有培养潜力”而“思考痕迹”是判断潜力的重要依据。举个例子动态规划题如果不会写转移方程你可以先写出暴力递归版本再写一句“这里有重复计算可以用一个二维数组记录中间结果来优化”。这个过程本身已经展示了你对动态规划思想的理解至少能拿一半的分数。类似的智力题如果推不出来最终答案把你画的状态推演表留在卷面上阅卷人也会认为你具备一定的分析能力。还有一点需要注意程序题的答题区域不要在没想清楚的情况下就涂涂改改。可以用草稿纸先理一遍思路再往答题区誊写。有些线上笔试系统不提供草稿纸那你就在代码注释里写思路写完思路再写代码这样阅卷人看到注释就知道你不是在瞎编。4.3 环境和工具准备的细节2016年那会儿的线上笔试系统体验参差不齐有些支持代码高亮和自动补全有些就是一个纯文本框连缩进都要手动敲。现在虽然好了一些但依然存在各种变量。所以笔试前一定要提前熟悉考试平台的操作不然到了考场上连“怎么提交代码”都要找半天心态就容易崩。如果是线上笔试提前做这几件事准备一个稳定的网络环境调试好浏览器和编辑器本地装好可以离线运行的编程环境比如Python或者Java环境准备一张草稿纸和两支笔用来推演链表和二叉树的结构变化如果允许把常用的代码模板准备好比如二分查找、链表反转、快排这些高频代码提前在本地编辑器中存好进场后可以快速调用思路。不同公司的笔试还有些小差异有的要求全程开摄像头有的要求共享屏幕这些都要提前看到通知然后做好准备。我当时有一次笔试就是没提前测试摄像头开考后折腾了10分钟才搞定白白浪费了宝贵的答题时间。这些细节虽然不起眼但确实能影响发挥。5. 备考中的典型误区与避坑心得5.1 只见题海不见原理备考笔试时很多同学会陷入“刷了500道题就稳了”的误区。但真实情况是如果只刷题不总结同类型的题换了个问法照样不会。我见过太多同学刷了几百道LeetCode但让他手写一个二分查找依然会在边界条件上犹豫半天。原因就是刷题时只看了题解没有真正理解那道题背后的“为什么”。备考比较有效的做法是“按专题刷题总结套路”。比如花两周专门刷链表题刷完每个题目都问自己三个问题这道题的核心思路是什么用了什么技巧如果换一种数据结构比如改成数组解法会有什么变化这样总结下来你会形成一套自己的“解题工具箱”而不是一个零散的题单。数据结构永远是那几种算法思想也永远逃不开那些招数抓住本质远比刷题数量重要。5.2 只看不写考场手生另外一个比较常见的坑是看题解觉得自己都懂了但一到了手写代码就写不流畅。这是因为“看懂代码”和“写出代码”是两种完全不同的能力。笔试场上没有IDE提示没有自动补全一切都是靠手敲如果平时没有练习过手写代码写的时候经常会出现语法错误、缩进错乱、循环边界想不清楚这些低级问题。所以我建议备考期间每天保持1到2小时的手写代码时间。可以用白板、用纸笔、用一个不带语法提示的纯文本编辑器自己从零开始写写完再对照标准答案看差距。坚持两到三周手写代码的流畅度和准确度会有明显提升。这个过程虽然枯燥但确实是最接近笔试现场的训练方式了。5.3 忽视网络与操作系统的性价比很多人备考时把所有精力都喂给了算法题操作系统和计算机网络这两块却草草带过。我觉得这个策略不太划算。从投入产出比来看网络和操作系统的基础题通常比较简单只要把最核心的概念理解清楚拿分反而比一道中高难度的算法题容易得多。算法题做得再多遇到新题也可能卡壳但基础概念题只要背熟原理大概率都能答出来。我当时给团队整理校招题目时看过一个统计数据在笔试总分差不多的情况下操作系统和网络部分得分高的人更容易通过后面的面试。原因也不难理解这两块能反映一个候选人对计算机系统是否有整体性了解而面试官通常都会对这类候选人有更好的印象。所以备考时千万别轻视这些“八股文”它们可能就是你和其他候选人拉开差距的地方。6. 这套笔试题在后来的工作里给我留下的东西6.1 基础扎实的人在真实工程里赢在哪里说实话很多笔试考点不会直接在工作里用到不太可能每天写一遍链表反转或者手写快排。但准备笔试过程中建立的底层认知会以另一种方式影响你的工作。比如你现在做接口开发如果理解TCP的状态流转调接口超时的时候就能猜到可能是哪一层出了问题而不是只会无脑重试你做性能优化如果理解哈希表在数据量变大时会rehash就能知道为什么有些操作在数据量上来后会突然变慢。我刚工作那几年接过线上服务频繁Full GC的问题排查了很久找不出原因后来突然想到是不是用了某些不合理的字符串拼接方式导致产生大量中间垃圾对象。这个排查思路不是当时学的什么具体算法而是对内存管理和对象生命周期这些基础概念有概念后自然而然形成的。所以别觉得笔试基础题和工作无关它们是在帮你建立一套“底层直觉”遇到问题的时候你会下意识往正确的方向想。6.2 如何持续保持底层基本功不退化基本功这种东西学了之后不维护是很容易退化的。我在团队里招人时经常遇到工作了三五年的人基础概念反而比应届生还模糊。这不能全怪个人毕竟工作内容很具体平时很难接触到那些通用底层知识。但作为工程师如果想要在技术路线上走得远一些保持基本功的活跃度还是很有必要的。我自己的做法是定期翻一翻经典书或者找一套这类的经典笔试题来重做题目本身做不做对无所谓主要是借题来检查自己对哪些基础概念的理解还没有过时。每年我也会参加几场线上的编程赛事或者在一些刷题平台上随机做一两道“每日一题”。这种低频但持续的训练比突击式的学习更能维持状态。另外如果团队里有新人或者实习生帮他们review笔试题、模拟面试也是很好的复习机会。教是最好的学给新人讲清楚进程和线程的区别表面上是帮别人实际上你会被迫把自己的理解重新整理一遍很多以前模糊的地方会在这个过程中被理清。这也是很多资深工程师推荐“费曼学习法”的原因——多输出、多讲、多复盘基本功就不容易丢。回到搜狐2016这套笔试题本身我的最终建议是别把它当成一次性的应试材料看完就扔。花两三个小时认真做一遍然后把做错的题梳理一下看看错题背后对应的是哪块基础没有夯实这才是这套题最大的价值。知识会更新题目会过期但扎实的底层能力和成体系的思考方式什么时候都用得上。
返回列表