
换一行我把当年考过的那套搜狐2013校招研发工程师笔试题翻出来重新看了一遍。这件事挺有意思虽然卷子已经过去很多年但里头的考点其实一点都不过时。指针、内存布局、进程线程、TCP状态机、Linux下的文件处理……今天互联网公司在校招笔试里翻来覆去考的还是这些东西。所以我决定把这块内容整理成一篇系统的复盘不光是贴答案而是把每类题目背后的考察意图、标准解法和容易踩的坑都说清楚。不管你现在是大三准备暑期实习还是已经工作几年想补一补基础这套题都值得认真过一遍。1. 内容整体设计与思路拆解当年校招笔试到底在考什么1.1 这套试题的板块构成与出题逻辑搜狐2013校招研发工程师笔试题属于比较典型的互联网公司技术笔试结构整体可以分成四大部分C/C语言基础、数据结构与算法、操作系统与网络、Linux与Shell实战。有些卷子还会穿插几道智力题或数学概率题用来考察逻辑思维和临场反应。当年这套题目的一个显著特点是它不追求偏题怪题而是把考察重心放在工程师日常开发中真正高频使用的核心知识点上。比如C/C部分一定会考指针和内存管理数据结构部分几乎必有链表反转和快排操作系统部分绕不开进程线程区别和死锁网络部分必考TCP的三次握手与四次挥手。这些题目属于“基础中的基础”但越是基础的题目越能拉开差距。原因很简单——基础不牢的人一考一个准基础扎实的人闭着眼睛都能写对。1.2 为什么这些考点到现在还有参考价值有人可能会说2013年的题目太老了现在技术栈都变了还看它干嘛。这个观点我部分认同但不完全对。技术栈确实在变但底层的计算机基础知识几乎没有变过。今天写Java、Go、Python的人照样要面对内存管理和并发问题今天做后端开发的人照样要懂TCP、HTTP、进程线程今天做数据开发的人照样要会写Shell脚本处理日志文件。这套笔试题考的是“内功”不是“招式”。内功扎实的人学什么新框架都快内功不行的人换个语言照样踩坑。从应聘角度看这套题目还有一层参考价值它代表了一类经典出题风格。搜狐当年的笔试题目和百度、腾讯、阿里的校招笔试题有很高的重叠度考点和难度曲线都很接近。把这类题目练熟再去参加其他公司的笔试会发现很多题目只是换了个外壳内核完全一样。2. C/C核心考点指针、内存与字符串2.1 指针与内存管理的经典陷阱C/C部分是整套笔试的硬骨头也是最考验基本功的地方。指针这一块几乎每年都会出现类似这样的问题sizeof一个指针和sizeof一个数组分别得到什么很多考生在这道题上栽跟头。在32位系统里指针大小是4字节在64位系统里是8字节而数组的sizeof返回的是数组占用的总字节数。比如char str[] hellosizeof(str)是6包含结尾的\0而如果函数参数是char* str在函数内部sizeof(str)就是指针的大小。这个考点背后考察的是“数组名和指针的区别”。数组名在大多数表达式中会退化为指向首元素的指针但在sizeof运算符里数组名保留的是数组的身份。这类题目没有太多技巧关键在于理解C语言类型系统的基本规则。内存泄漏问题也是笔试常客。题目通常会给你一小段代码让你找出内存泄漏的位置。最常见的模式是函数内malloc了一块内存某些分支提前return了导致后面的free没有执行。这种题考查的不是你会不会用free而是你有没有在写代码时保持“每个分配都有对应释放”的严谨习惯。有一个很实用的检查方法看到malloc或new立刻在当前函数的返回路径上找对应的free或delete没有就是泄漏。2.2 字符串处理与边界条件字符串是C/C笔试的另一个高频考点。手写strcpy、strlen、strcmp这类题目出现频率极高而且考官喜欢在边界条件上做文章。比如strcpy函数标准答案要处理源字符串为空的情况、目标缓冲区不足的情况、源和目的重叠的情况。当年有一道题就是关于strcpy和memcpy的区别以及当源和目的内存区域发生重叠时应该用哪个。正确答案是memmove因为memmove处理了重叠场景而memcpy不保证这一点。这类题给我的启发是笔试不光考你会不会写代码更考你有没有完备的工程思维。边界条件和异常处理恰恰是区分“能跑”和“靠谱”的分界线。建议刷题时多花时间在一类特殊输入上——空指针、空字符串、长度为1的字符串、超大输入。把这些情况考虑周全写出来的代码质量立刻上一个台阶。2.3 实战案例一个交换函数看出基本功当年搜狐的笔试题里有一个非常经典的函数交换两个变量的值。看起来简单到不能再简单但这里藏着好几个层次的考察点。第一个层次直接写void swap(int a, int b) { int temp a; a b; b temp; }这个写法是错误的因为C语言函数参数默认是值传递函数内部的修改不影响外部变量。正确做法是传指针void swap(int *a, int *b) { int temp *a; *a *b; *b temp; }第二个层次面试官会追问不用临时变量能不能交换很多人会想到异或方式*a ^ *b; *b ^ *a; *a ^ *b;这个写法确实能交换但有一个致命陷阱如果a和b指向同一个地址第一步异或就把值清零了。所以让用异或交换时必须加一个判断if (a b) return;。这道题的价值在于提醒我们任何“巧妙”的写法都要考虑极端情况。工程中我更推荐朴素的临时变量写法因为可读性好而且现代编译器优化后性能几乎没有差别。第三个层次交换两个int用位运算还是临时变量那交换两个链表节点呢交换两个文件内容呢这就要上升到抽象思维了。笔试考的是具体的函数写法但背后考察的是你能不能把“交换”这个操作抽象出来应用到不同场景。这种抽象能力在真实开发中比会写某个具体函数值钱得多。3. 数据结构与算法链表、栈队列、排序查找3.1 链表操作反转、环检测、交点问题数据结构部分链表题绝对是考试主力。单链表反转、检测链表是否有环、找两个链表的交点这三道题几乎是互联网公司笔试的“标配”。单链表反转是一个必背题目但我不建议死记代码而是要理解迭代和递归两种思路。迭代写法是用三个指针prev、current、next在遍历过程中逐个反转节点的指向时间复杂度O(n)空间复杂度O(1)。递归写法核心是假设后续链表已经反转然后把当前节点接到尾部最好用递归函数完整推演几个节点来理解不要凭空想。检测链表是否有环的标准解法是快慢指针快指针每次走两步慢指针每次走一步如果链表有环两者必然相遇。这里有一个关键细节为什么快指针走两步而不是三步四步因为两步可以保证慢指针进入环后快指针相对于慢指针每次只追一步必然在有限次内追上。如果步数差太大可能出现快指针在慢指针进入环之前反复跳过慢指针增加分析复杂度。这个推演过程笔试不一定直接考但面试官可能追问。找两个链表交点的题比较经典的解法是先分别遍历两个链表得到长度让长的链表先走差值步然后两个指针同步前进第一个相等的位置就是交点。还有一种更简洁的玩法两个指针分别从两个链表头出发走到尾部后跳到另一个链表头部继续走如果链表有交点两个指针一定会在交点处相遇。这个解法理解起来比长度差方法绕一些但代码量更短而且能反映出你对“路径长度”这个概念的理解深度。3.2 栈与队列两个栈实现队列用两个栈实现队列是一道非常经典的设计题。这道题在很多人的认知里就是抄一个标准答案但很少有人真正理解为什么这么设计。我来把思路重新梳理一遍。栈的特点是先进后出队列的特点是先进先出。用两个栈实现队列的经典方案是一个栈负责入队操作入队栈另一个栈负责出队操作出队栈。入队时直接压入入队栈出队时如果出队栈不为空直接弹出栈顶如果出队栈为空就把入队栈的所有元素依次弹出并压入出队栈然后再弹出栈顶。这个设计的核心思想是“两次先进后出抵消得到先进先出”。元素经过入队栈的倒序和出队栈的再次倒序顺序就恢复成原始顺序了。理解了这个原理你就不需要背代码而是可以现场推导。这种设计题考察的其实是“你能否用已有工具组合出新的行为”在真实开发里很多时候我们就是在用基础组件拼装满足特定需求的模块思路是完全一致的。3.3 排序与查找手写快排的坑与优化手写快速排序是笔试题的保留项目但很多人在白板上能写出来跑起来却各种问题。常见的问题集中在递归边界写错导致死循环、partition时选中间元素还是选首元素、数组下标处理越界。排序算法有几个高频追问点快速排序的平均时间复杂度和最坏时间复杂度分别是多少答案是平均O(nlogn)、最坏O(n^2)最坏情况发生在每次划分都极度不均匀时比如数组已经有序且每次选首元素作为基准。如何避免最坏情况常见做法是随机选择基准元素或者取首元素、中间元素、尾元素的中位数作为基准。还有稳定性的问题快排是不稳定的而冒泡、插入、归并是稳定的。另一个高频考点是二分查找。代码简单但边界条件非常容易出错。我特别强调一个细节计算中间位置时用mid left (right - left) / 2不要用mid (left right) / 2。前者能避免left right溢出。同时要注意循环不变量是while (left right)还是while (left right)决定了right的更新方式。从这套题的经验来看很多程序员写了很长时间的业务代码却在这类最基础的题目上翻车说白了就是平时用框架用多了手生。4. 操作系统与网络进程线程、死锁与TCP4.1 进程与线程的区别从底层聊起操作系统部分的必考题第一个肯定是进程和线程的区别。笔试考的是概念但要把概念讲清楚必须涉及底层实现。进程是操作系统资源分配的基本单位拥有独立的地址空间、文件描述符表、信号处理器等线程是CPU调度的基本单位多个线程共享所属进程的地址空间和资源。线程切换比进程切换开销小因为不需要切换页表等重量级资源。考察进程间通信方式也是高频题目管道、命名管道、消息队列、共享内存、信号量、套接字。其中共享内存是最快的IPC方式因为它不需要内核态和用户态之间的数据拷贝但需要配合信号量解决同步互斥问题。真实开发中“共享内存信号量”的组合非常常见尤其在追求极致性能的场景下。线程同步方面互斥锁、条件变量、读写锁、信号量和自旋锁的区别也常被问到。一个容易混淆的点是互斥锁和自旋锁的区别互斥锁在拿不到锁时会让线程进入睡眠状态唤醒后有上下文切换开销自旋锁则是一直忙等待不切换线程所以适合临界区很短的情况。如果临界区代码执行时间较长用自旋锁会浪费大量CPU。4.2 死锁的四个必要条件与银行家算法死锁几乎是每套笔试题的必考内容。死锁产生的四个必要条件是互斥条件资源不能被多个进程同时占用。持有并等待条件进程持有至少一个资源同时等待获取其他进程持有的资源。不可剥夺条件进程已获取的资源不能被强制剥夺只能由持有者主动释放。循环等待条件存在一个进程资源的循环等待链。笔试常见问法是“给出四个条件如何破除死锁”。一般思路是破坏任何一个条件都能预防死锁。比如破坏“持有并等待”可以让进程一次性申请所有资源破坏“不可剥夺”可以在进程无法获得全部所需资源时释放已持有的资源破坏“循环等待”可以给资源编号强制进程按编号递增的顺序申请资源。死锁避免的经典算法是银行家算法核心思想是在分配资源之前系统判断分配后是否仍然处于安全状态只有安全才分配。我在笔试和面试中的体会是死锁概念本身不难但真正理解需要结合实践。实际项目中分析死锁问题时第一步永远是定位“等待关系图”看是否存在循环等待而不会去死背四个条件。笔试考死锁核心目的是检验你有没有这种“从资源关系出发分析并发问题”的意识。4.3 TCP三次握手与四次挥手隐藏考点网络部分TCP三次握手和四次挥手是绝对重点。三次握手的过程可以简单概括为客户端发SYN服务端回SYNACK客户端再回ACK。但隐藏考点非常多。为什么需要三次握手最基本的理由是三次握手能确认双方的收发能力都正常。第一次握手服务端确认客户端发送能力正常第二次握手客户端确认服务端收发能力正常第三次握手服务端确认客户端接收能力正常。同时三次握手还能避免历史重复连接请求导致的资源浪费。如果只有两次握手客户端一个迟到的旧连接请求可能让服务端误建连接。这部分分析面试官特别爱追着问建议把每条状态变化都搞清楚。四次挥手过程则涉及主动关闭方和被动关闭方不同的状态变化。主动关闭方发送FIN后进入FIN_WAIT_1收到对方ACK进入FIN_WAIT_2收到对方FIN后回复ACK并进入TIME_WAIT等待2MSL后关闭。TIME_WAIT存在的意义主要有两个一是确保最后的ACK能到达对方如果丢了可以重发二是让本连接产生的所有旧报文在网络中自然消失避免影响新连接。笔试经常问TIME_WAIT时间为什么是2MSL因为在理论上需要1个MSL确保最后的ACK到达对方另外1个MSL等待对方可能重发的FIN。实际上这几乎就是纯概念但是理解状态机非常有用排查网络问题时你必然要读懂netstat和ss输出里的各种状态。5. Linux与Shell笔试中的实战题5.1 高频命令查看端口、进程、磁盘、日志搜狐这套笔试题有一个很务实的部分就是Linux操作和Shell脚本。这部分题目和实际工作场景结合得特别紧。考察内容包括如何查看某个端口是否被占用、如何查看系统资源使用情况、如何查找最近修改过的文件、如何统计日志文件中的某个关键字出现次数。先看具体命令。查看端口占用经典命令是netstat -tlnp | grep 8080-t表示TCP-l表示监听状态-n表示以数字形式显示地址和端口-p表示显示进程PID和名称。新一点的系统推荐用ss命令输出更快更清晰ss -tlnp | grep 8080。这两条命令在实际排查问题时的使用频率非常高比如启动服务时发现端口被占就要立刻查是哪个进程占了端口。查看进程和系统负载ps aux能列出所有进程的CPU和内存占用配合grep按关键字过滤比如ps aux | grep java。top命令可以实时展示系统整体负载是按CPU使用率排序的。查看磁盘空间df -h按人类可读格式显示文件系统使用量du -sh *显示当前目录下各子目录的总大小。查看日志文件tail -f可以持续跟踪文件新增内容这是排查线上问题的常用手段。5.2 Shell文本处理awk与sed三板斧Shell部分最让人头疼的往往是文本处理三剑客grep、awk、sed。笔试和实际工作中经常要求用一条命令完成统计、提取和替换操作。grep最基础作用是在文本中按正则表达式匹配行。grep ERROR app.log能快速过滤出包含ERROR的行。加-c参数统计匹配行数-v反向匹配-E支持扩展正则。日常排查日志三者的组合几乎是必备技能。awk的核心用法是按列处理文本。awk {print $1, $3} file能提取每行的第1列和第3列。awk还内置NR行号和NF列数等变量比如awk NR10 NR20 file可以取第10到20行。更强大的是awk支持BEGIN和END块可以用来做累加统计。比如统计日志中某个字段的和一条命令就能搞定。sed的强项是流式编辑典型的替换写法是sed s/old/new/g file把每行中的old替换成new并输出到标准输出。-i参数可以原地修改文件但使用时要特别小心最好先不加-i跑一遍确认输出符合预期再真正执行替换。我在实践中的习惯是频繁用grep和awk提取数据用sed做文本替换三者组合可以解决大部分日志处理和配置文件修改的需求。这套技能在笔试中几乎等于送分题但前提是你真的在Linux环境里用过而不是只背参数表。6. 笔试复盘与答题策略从做题到面试的跨越6.1 一道经典概率题的典型解法搜狐那套题里有一道概率相关的题目题目大意是假设有1000瓶药水其中1瓶是毒药小白鼠服用后会在24小时内死亡现在只有24小时时间最少需要多少只小白鼠可以找出毒药瓶。标准答案是10只每只小白鼠对应二进制的一位混合若干瓶药水给对应的小白鼠服用24小时后根据死亡情况组合出毒药瓶编号。这道题考察的是计算机科学里的二进制编码思想。1000在二进制下需要10位2^101024每一位的小白鼠要么死要么活所以10只小白鼠正好能表示1024种状态足以区分1000瓶药水。解答思路是先把药水瓶从1到1000编号转成二进制按位分组混合喂药最后根据死亡的小白鼠还原毒药编号。这类题的价值在于它考察的并不是概率知识本身而是“信息编码”的思维。计算机领域的很多问题本质上是信息表示问题能想到用二进制编码说明你具备将现实问题映射到计算机数据结构的能力。面试官出这类题其实是观察你的思维过程是直接放弃还是能找到切入点并把问题拆解成可计算的形式。6.2 时间分配与答题顺序先易后难的实战建议从笔试实践来看答题顺序直接影响最终得分。我的经验是拿到卷子先快速浏览一遍把题目分成三类——第一类是“扫一眼就知道答案”的基础题第二类是“需要想一下但能写出来”的中等题第三类是“暂时没思路”的难题。先做第一类把该拿的分全部拿到再做第二类最后集中精力攻克第三类。编程题和逻辑题放在动手写代码的环节不能和基础题混在一起耗时。时间分配上每道题都要有明确的时间上限。比如基础概念题每题控制在3分钟以内写代码题每题控制在15到20分钟以内如果超过时间还没写完先写下一题回头再补。这个建议尤其适用于互联网公司的笔试题目量大、时间紧最常见的情况是前面基础题耗时过多导致后面的大题完全没时间做反而丢了大头分数。另一个容易被忽略的小细节是保证代码的格式清晰、边界条件完整。笔试大题往往不是完全自动判分人工也会看你的代码风格和注释。在实现核心功能之外用空行把逻辑分段、在函数开头加一行注释说明输入输出这些小细节能在阅卷时给你加不少印象分。6.3 从笔试看面试如何把题面变成展示机会笔试和面试是紧密关联的。笔试中考到的知识点面试环节常常会继续追问只是形式变了。比如笔试写了快排面试可能会问“快排最坏情况是什么如何优化如果数据量特别大快排还能用吗”笔试题提前帮我们划定了面试复习范围。我建议准备校招时不要把笔试和面试割裂开。准备笔试题的过程中每一个考点都要多问一句“如果我是面试官我会继续问什么”。比如复习TCP三次握手要能同时答出SYN攻击原理、TIME_WAIT作用、TCP连接状态迁移复习链表反转要能一下子说出迭代和递归两种写法各自的复杂度以及如何用快慢指针检测环。把这两个问题打开笔试和面试就同时准备了。还有一点面试时如果遇到笔试题不会做不必完全放弃。你可以把这个题目当成一次交流机会把自己能想到的思路、尝试过的方向都说出来哪怕最后没有写对代码也能让面试官看到你的思考过程。面试官要的不是完美的答案而是有潜力的候选人。从这点来说笔试之前的大量复盘不只是为了做题更是为了在面试中展现真实的工程思考能力。复盘完这套题我最大的体会现在回头重新看这套2013年的搜狐笔试题我能明显感觉到一个趋势校招笔试的重点从来不是刁难人而是筛选那些基础扎实、思路清晰、有工程习惯的候选人。很多年后你面试别人时也会发现一个人C语言指针、数据结构和网络基础是不是真的牢固其实几张卷子就能看出来。所以不管你是即将参加校招还是已经工作多年想把基础系统补一遍我都建议认真过一过这类经典真题不要觉得它们“过时”。技术会迭代框架会更新但计算机科学里那些核心的知识——内存怎么管理、数据怎么组织、进程怎么通信、网络怎么交互——几十年都没变过。最后再说一个我自己的习惯每次做完一套题不要只看对错而是花半小时把每道错题背后的知识点重新梳理一遍写在自己的技术笔记里。这种方法比盲目刷十套卷子都管用。知识体系就是这样一点点搭起来的笔试考的好不好是暂时的过程中积累的理解才是长在自己身上的能力。