ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

搜狐研发工程师笔试题复盘:从基础原理到工程思维的完整解析

搜狐研发工程师笔试题复盘:从基础原理到工程思维的完整解析 想进老牌互联网公司做研发笔试这一关总绕不开。我翻出早年整理的搜狐2016研发工程师笔试题时发现一个有意思的现象这套题放在今天依然有很强的参考价值它考察的不是你背了多少API而是你有没有真正理解计算机基础原理。当年我在牛客网刷这套题时栽了不少跟头后来面过几家大厂回头看这套题它的出题思路其实非常典型——数据结构、操作系统、网络、数据库、逻辑思维全覆盖难度梯度也安排得很合理。这篇文章我就把当年做这套题的完整复盘、解法推导与踩坑记录整理出来给正在准备校招或社招笔试的同学做个参考。1. 这套题的整体脉络搜狐研发岗在筛选什么样的人先说结论搜狐2016年的研发笔试题整体风格偏基础思维不追新框架、不考偏门语法但非常注重对底层原理的理解深度。整套题可以明显分成几大板块——数据结构与算法、操作系统、计算机网络、数据库、逻辑推理与智商题、编程题。这种结构在今天依然是互联网公司校招笔试的主流模板。从筛选逻辑来看这套题其实在考察三件事基础是否扎实链表操作、二叉树遍历、内存管理、TCP协议这些计算机核心知识有没有形成体系而不是零散记忆。思维是否严谨很多题目都有边界条件陷阱比如字符串处理中的空串、溢出递归中的终止条件。能写对是一回事能考虑全面是另一回事。工程意识是否萌芽编程题虽然难度不算顶尖但要求代码结构清晰、变量命名规范、有必要的注释和错误处理。这一点很多刷题党容易忽略。我当年做这套题时最大的感受是它不会刻意刁难你但如果你只是背过答案而没有真正理解原理很容易在变式题上翻车。比如有的题稍微改一下条件就能筛掉一批只会套公式的候选人。2. 链表与指针搜狐笔试里反复出现的类型题2.1 单链表反转两种解法与一个经典隐藏坑单链表反转基本是互联网公司笔试的送分题但恰恰是这种题最能拉开差距。搜狐的考察方式比较直接给你一个单链表头指针要求反转后返回新链表的头结点。// 迭代法面试中最推荐 struct ListNode* reverseList(struct ListNode* head) { struct ListNode* prev NULL; struct ListNode* curr head; while (curr ! NULL) { struct ListNode* nextTemp curr-next; // 先保存下一个节点 curr-next prev; // 反转指针 prev curr; // 前驱后移 curr nextTemp; // 当前后移 } return prev; }迭代法的时间复杂度O(n)、空间复杂度O(1)这是最优解。但当年笔试时不少人在一个细节上栽了跟头没有提前保存next节点就修改当前节点的next指针导致链表断链。这道题如果写成递归版本还需要注意递归深度问题——链表长度很大时可能会导致栈溢出笔试时如果题目没有明确说明链表长度建议优先写迭代版本。2.2 判断链表是否有环快慢指针的数学原理判断链表是否成环也是搜狐及各大厂笔试的常客。解法大家都知道用两个指针一个每次走一步一个每次走两步如果相遇说明有环。但很多人只是知道解法却不明白为什么可行。这里补一下数学推导假设链表无环部分的长度为a环的长度为b。当慢指针进入环时快指针已经领先它若干个节点。因为快指针每次比慢指针多走一步所以经过有限步后快指针必定能追上慢指针。关键是链表中不存在无限递进的条件环让追及过程变成一个模b同余的数学问题——快指针的步数差总能被b整除因此必定会在某个节点相遇。这个推导过程在搜狐的面试环节中往往会追问笔试虽然只要求代码但搞清楚原理对后续面试很有帮助。另外如果题目要求判断环的入口位置需要用相遇后一个指针从头开始两个指针同步走再次相遇即为入口的技巧这个结论也要能推导出来。2.3 两个链表的第一个公共节点栈、哈希与双指针的取舍搜狐这套题里还有一道经典题——找两个单链表的第一个公共节点。三个可行方案哈希表法遍历第一个链表把所有节点存入哈希表然后遍历第二个链表第一个在哈希表中出现的节点就是公共节点。时间复杂度O(mn)空间复杂度O(m)。栈法两个链表分别入栈然后同时出栈最后一组相同的节点就是第一个公共节点。空间复杂度较高。双指针法两个指针分别遍历两个链表当某个指针走到末尾时让它跳到另一个链表的头部继续走。因为两个指针走过的总路程相等所以它们一定会在公共节点相遇。时间复杂度O(mn)空间复杂度O(1)。笔试和面试中最推荐双指针法但要注意一个边界条件如果两个链表没有公共节点两个指针会同时走到NULL此时循环条件要设置好防止死循环。3. 二叉树与递归笔试中占比最高的数据结构考察3.1 二叉树遍历的非递归实现从栈模拟到Morris遍历搜狐笔试题里二叉树遍历几乎是必考项。递归版本人人都能写但非递归版本才是真正的分水岭。尤其是后序遍历的非递归实现当年考倒了一批人。先序遍历和中序遍历的非递归实现都相对直观用栈模拟系统调用栈即可。后序遍历的难点在于需要判断右子树是否已经访问过。一个常见的解法是使用两个栈// 后序遍历非递归版两个栈法 void postorderTraversal(TreeNode* root) { if (!root) return; stackTreeNode* s1, s2; s1.push(root); while (!s1.empty()) { TreeNode* node s1.top(); s1.pop(); s2.push(node); if (node-left) s1.push(node-left); if (node-right) s1.push(node-right); } while (!s2.empty()) { cout s2.top()-val ; s2.pop(); } }这个解法的核心思路是s1按照根-右-左的顺序出栈s2接收后输出顺序就是左-右-根。如果想挑战更高级的做法可以尝试Morris遍历——空间复杂度降为O(1)但需要修改树的结构临时建立线索笔试中没把握不推荐使用因为改坏了树结构反而扣分。3.2 平衡二叉树判断自底向上的递归搜狐这套题中有一道判断一棵二叉树是否为平衡二叉树。很多人的第一反应是写出求树高的函数然后对每个节点都调用一次自上而下判断。这个方案能通过但时间复杂度是O(n²)效率较低。更优的方案是自底向上递归在计算树高的同时判断以当前节点为根的子树是否平衡。如果左右子树的高度差超过1直接返回-1作为不平衡标记否则返回真实高度。这样每个节点只访问一次时间复杂度O(n)。int checkBalanced(TreeNode* root) { if (!root) return 0; int left checkBalanced(root-left); if (left -1) return -1; int right checkBalanced(root-right); if (right -1) return -1; if (abs(left - right) 1) return -1; return max(left, right) 1; }这道题的考察点不仅在于会不会递归更在于有没有复杂度优化的意识。搜狐的研发团队规模不小代码要处理的数据量可能很大这种优化意识在实际工程中很重要。3.3 二叉树层序遍历换行版的变式怎么处理层序遍历本身不复杂用队列即可完成。但搜狐当年的题目增加了一个条件要求按层输出每层结果单独放在一个数组里。这就需要在BFS中记录每一层的节点数量。关键写法是每次进入外层循环时先获取队列当前的size这个size就是当前层的节点数量然后内层循环处理完这些节点。这样才能区分层与层的边界。这个技巧后来我在无数公司的笔试中反复用到比如二叉树右视图、之字形遍历本质都是按层处理思路的变形。4. 操作系统核心题进程、线程、内存与死锁4.1 进程与线程的区别不只是资源分配与调度的基本单位搜狐笔试中有一道老生常谈的简答题进程和线程的区别。这种题看似简单但拿高分不容易。只回答进程是资源分配的基本单位线程是调度的基本单位只能得基础分要把以下几点补充完整地址空间进程拥有独立的地址空间线程共享所属进程的地址空间。因此一个线程崩溃可能导致整个进程崩溃而进程间通常互不影响。资源开销进程切换需要切换地址空间、页表、文件描述符等开销较大线程切换只需保存和恢复寄存器及栈指针开销较小。通信方式进程间通信需要通过IPC机制管道、消息队列、共享内存、信号量、socket等线程间通信可以直接通过共享的全局变量或堆内存完成但需要同步机制。健壮性与隔离性进程之间隔离性好、安全性高适合多任务部署线程之间共享资源协作效率高但保护难度大。我当时在答题时画了一个简单的对比表格把地址空间、资源、切换开销、通信、崩溃影响五个维度逐一说明。笔试是人工阅卷有条理的答案更容易得高分。4.2 内存管理堆与栈的区别及内存泄漏的成因堆与栈的区别是搜狐笔试题的另一个高频考点也是面试官喜欢深挖的题目。可以从以下几个维度回答分配方式栈由编译器自动分配和释放存放函数的参数值、局部变量等堆由程序员手动申请和释放C语言用malloc/freeC用new/delete。内存方向栈向低地址扩展堆向高地址扩展。分配效率栈的分配是寄存器和栈指针直接操作效率高堆的分配需要算法查找合适的空闲块效率相对较低。大小限制栈的大小在程序启动时确定通常几MB堆的大小受限于系统虚拟内存可以很大。内存泄漏的问题在这道题中也会被追问。C/C程序中最常见的内存泄漏类型包括忘记free或delete、基类析构函数没有声明为virtual导致派生类析构不完整、容器中存放的指针在clear时没有释放指向的对象、循环引用C中特指共享指针的循环引用。回答时如果能结合具体的代码反例来说明会比单纯罗列概念更有说服力。4.3 死锁产生的四个必要条件与破局思路死锁是一个理论性很强的考点搜狐的题目风格是直接问给场景双管齐下。四个必要条件是死锁问题的根基互斥条件资源一次性只能被一个进程占用。持有并等待条件进程在持有至少一个资源的同时又在等待其他进程持有的资源。不可剥夺条件进程已获得的资源在未被使用完前不能被其他进程强行夺走只能由持有者主动释放。循环等待条件存在一条进程-资源的环形链链中每个进程都在等待下一个进程持有的资源。破除死锁的思路从这四个条件反推即可破坏互斥很难很多资源本质就是互斥的、破坏持有并等待一次性申请所有资源、破坏不可剥夺允许资源剥夺、破坏循环等待给资源编号按序申请。搜狐笔试中如果出现给出一种破除死锁的方法并说明破坏哪个条件这类题按这个框架答就会很稳。5. 网络协议TCP与HTTP的细节把控5.1 TCP三次握手与四次挥手除了状态转换还要会什么TCP连接管理是网络部分的必考题。搜狐的考察并不停留在三次握手是什么而是更关注理解深度。比如会问为什么建立连接需要三次握手而不是两次因为两次握手无法防止失效的连接请求报文段突然到达服务端。如果A发出的第一次连接请求在网络中滞留A超时后重新发送服务端收到滞后的旧请求并建立连接就会造成资源浪费。三次握手通过A第三次发送确认来避免这一问题。为什么断开连接需要四次挥手因为TCP是全双工通信两个方向的关闭需要独立完成。A发送FIN只表示A不再发送数据但B可能还有数据要发给A所以B先发送ACK确认等B的数据发送完毕后再发送FIN。这正是两个方向各需要一次FINACK的原因。TIME_WAIT状态为什么需要等待2MSL一是确保最后的ACK能够到达对端如果丢失让对端重发FIN二是让本连接产生的所有报文在网络中消失避免影响后续连接。我觉得这类题拿高分的核心是不要只背状态名要能画出状态转换图并解释每个状态迁移背后的为什么。笔试时用文字简单的状态描述组合作答会比纯文字更有条理。5.2 HTTP与HTTPS状态码、缓存与加密握手搜狐笔试题对HTTP协议考察得也比较细其中几个高频点包括常见状态码含义200 OK301永久重定向302临时重定向304 Not Modified命中缓存400 Bad Request401未认证403禁止访问404未找到500服务器内部错误502 Bad Gateway503服务不可用。GET与POST的本质区别GET请求参数在URL中安全性差、长度有限制、可以被缓存POST请求参数在请求体中相对安全、长度不受限、默认不可缓存。更深层的区别是语义与幂等性GET是安全且幂等的POST不是。HTTPS的加密握手过程客户端向服务器发送支持的加密协议版本和加密套件列表服务器选择加密算法并返回证书客户端验证证书合法性生成随机预主密钥并用服务器公钥加密后发送服务器用私钥解密得到预主密钥双方各自生成会话密钥之后用会话密钥进行对称加密通信。这里我建议准备一个大纲式的总结非对称加密用于密钥交换阶段对称加密用于数据传输阶段数字证书用于身份认证。这种三段式的回答结构在笔试阅卷中非常讨巧。5.3 DNS解析的全过程从浏览器到IP地址DNS解析也是搜狐网络题的一个考点。完整流程如下浏览器先查询浏览器缓存和本地hosts文件。未命中则向本地DNS服务器发起递归查询。本地DNS服务器如果没有缓存则向根DNS服务器发起迭代查询获得顶级域如.com服务器的地址。继续向顶级域服务器查询获得权威DNS服务器的地址。向权威DNS服务器查询域名对应的IP地址返回结果并缓存在本地DNS服务器中。浏览器收到IP地址后发起TCP连接请求。回答这道题时画出浏览器-本地DNS服务器-根服务器-顶级域服务器-权威服务器的查询链把递归查询与迭代查询的区别讲清楚基本就满分了。顺便补充一个容易考的点DNS为什么使用UDP而不是TCP因为DNS查询报文短小UDP开销小、速度快单个报文能装下查询结果但区域传送zone transfer会使用TCP因为数据量大需要可靠传输。6. 数据库索引原理与SQL设计的实战细节6.1 索引为什么能加速查询B树的前世今生数据库索引几乎是所有研发笔试的必考内容搜狐也不例外。理解索引加速的原理关键在理解B树的数据结构特征。B树相较于二叉搜索树和B树的优势在于高度可控一个节点可以存储多个关键字和多个子节点指针树的高度通常在3到4层。对一张千万级数据的表做查询只需要3到4次磁盘I/O。范围查询高效所有数据都存储在叶子节点且叶子节点之间通过链表指针串联。做范围查询时找到起点后可以直接沿着叶子链表顺序扫描无需回溯到上层节点。磁盘读写友好节点大小通常设置为操作系统页大小4KB或8KB的整数倍减少磁盘I/O次数。关于索引失效的考察也很常见。我在实际开发和笔试中总结出的几大索引失效场景对索引列使用函数或表达式计算。使用LIKE匹配时通配符在开头。联合索引不满足最左前缀原则。索引列发生隐式类型转换如字符串列与数字比较。使用OR连接的条件中一侧不是索引列。这些场景我建议结合具体的SQL语句来记忆笔试中遇到以下哪个SQL能用上索引这类题时逐项排查即可。6.2 事务的ACID特性与隔离级别数据库事务是搜狐的常客。ACID四个特性的含义要能用大白话解释清楚原子性Atomicity一个事务的所有操作要么全部成功提交要么全部回滚不存在中间状态。一致性Consistency事务执行前后数据库的完整性约束不能被破坏。隔离性Isolation多个事务并发执行时彼此之间不能互相干扰。持久性Durability事务一旦提交对数据的修改就是永久的。四个隔离级别从低到高依次是隔离级别脏读不可重复读幻读读未提交Read Uncommitted可能可能可能读已提交Read Committed不会可能可能可重复读Repeatable Read不会不会可能串行化Serializable不会不会不会MySQL的InnoDB引擎默认使用可重复读隔离级别且通过MVCC多版本并发控制间隙锁Gap Lock在可重复读级别下解决了幻读问题。这个细节是加分项建议笔试时写出来。6.3 一条SQL查询的执行流程搜狐如果出描述一条SQL从客户端到返回结果的完整过程这类题可以按MySQL的架构分为三层来回答客户端层客户端发送SQL语句建立与服务器的连接。服务器层连接器管理连接与权限验证查询缓存检查MySQL 8.0已移除此功能分析器做词法分析和语法分析生成语法树优化器决定使用哪个索引、调整表连接顺序等执行计划执行器调用存储引擎接口执行计划。存储引擎层InnoDB通过Buffer Pool进行数据页缓存通过Undo Log实现MVCC和多版本回滚通过Redo Log保证崩溃恢复能力。数据最终以页为单位存储在磁盘上。这道题回答得完整的话面试官会认为你真正理解MySQL的整体架构而不只是会写CRUD语句。7. 逻辑推理题拉开差距的思考方式7.1 经典智力题的通用解题策略搜狐笔试题中包含一些逻辑推理和智力题常见类型有鸽笼原理、排列组合、概率计算、天平称重找次品、赛马找最快等。这类题目目的不是考察高深数学知识而是考察逻辑思维的严谨性和结构化拆解能力。我的经验是逻辑题的高效解决路径有两条从极端情况入手比如1000瓶水中有1瓶毒药用多少只小白鼠能在24小时内找出毒药本质是用二进制编码来标记编号2的10次方等于1024所以需要10只小白鼠。从状态空间入手比如一个3升水桶和一个5升水桶如何量出4升水本质是模拟两个水桶的状态转移BFS搜索可行路径可以保证找到最优解。遇到这种题在草稿纸上画状态转移图或二进制编码表比空想要可靠得多。7.2 编程题中的思维考察边界条件与代码风格搜狐的编程题一般不会特别难但非常看重代码的完整度。我当年在这部分吃过亏总结了几条血泪经验先写注释再写代码标明函数功能、参数含义、返回值既方便自己理清思路也是给阅卷人看的工程素养。考虑空输入与单节点输入链表为空、二叉树为空、字符串为NULL、数组长度为0这些边界条件必须处理否则扣分。变量命名要见名知意不要用a、b、c用length、index、currentNode这类有语义的名称。时间空间复杂度要在注释里写明这既是考察点也是展示你算法素养的机会。public class Solution { /** * 计算数组中连续子数组的最大和 * param nums 输入数组 * return 最大连续子数组和 * 时间复杂度 O(n)空间复杂度 O(1) */ public int maxSubArray(int[] nums) { if (nums null || nums.length 0) { return 0; } int currentSum nums[0]; int maxSum nums[0]; for (int i 1; i nums.length; i) { currentSum Math.max(nums[i], currentSum nums[i]); maxSum Math.max(maxSum, currentSum); } return maxSum; } }这个最大连续子数组和问题用动态规划很容易解核心状态转移方程是currentSum max(nums[i], currentSum nums[i])。但如果笔试题目要求返回子数组的起始和结束下标就需要额外维护两个指针在每次更新maxSum时记录位置。这种至少会写一种变式的要求在搜狐的编程题中并不罕见。8. C/C与Java基础容易被忽视的失分点8.1 C/C中的指针与内存操作搜狐作为以C/C起家的公司研发工程师岗位的笔试题对指针的考察相当重视。常考的知识点包括指针和引用的区别指针是变量存储的是地址可以重新赋值指向其他地址引用是别名必须在定义时初始化且不能更改绑定的对象。指针数组与数组指针的区别int *p[10]是指针数组有10个指针元素int (*p)[10]是数组指针指向一个包含10个int的数组。malloc/free与new/delete的区别前者是库函数后者是操作符前者不会调用构造函数和析构函数后者会前者需要手动指定字节数后者由编译器根据类型自动计算。深拷贝与浅拷贝默认拷贝构造函数和赋值运算符是浅拷贝类中有指针成员时可能导致double free或悬空指针因此必须实现深拷贝并遵循拷贝三原则拷贝构造函数、拷贝赋值运算符、析构函数。8.2 Java中的内存分区与垃圾回收如果投的是后端Java岗位搜狐Java题部分的考察重点包括JVM运行时内存区域程序计数器、虚拟机栈、本地方法栈、堆、方法区元空间。其中堆是GC的主要工作区域虚拟机栈和方法区是线程隔离的区域。GC Root有哪些虚拟机栈中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象。从这些根对象出发的可达性分析决定了对象是否可以被回收。Java中和equals的区别比较基本类型时比较值、比较引用类型时比较地址equals默认也是比较地址但String类重写后比较内容。笔试中经常考察new String和字符串常量池的组合场景关键在于字符串常量池只存储直接赋值的字面量。8.3 内存屏障与并发编程基础搜狐Java题涉及并发时会考察volatile关键字、synchronized锁升级、CAS与AQS等。最常考的是volatile的内存语义保证可见性、禁止指令重排序、但不保证原子性。典型的使用场景是状态标志位和单例模式的双重检查锁。这个在并发编程章节详细展开。9. 并发编程考点从volatile到死锁避免并发编程是研发笔试中综合性最强的模块之一因为它同时涉及操作系统原理、编程语言特性和工程实践经验。搜狐的题目在这块的考察风格偏实用喜欢给出代码让判断是否有线程安全问题。9.1 volatile、synchronized与CAS的使用边界volatile解决的是多线程可见性和指令重排序问题但不解决复合操作的原子性。典型的反例是count即使count声明为volatile多线程执行时依然会丢失更新因为自增操作是读-改-写三步。synchronized解决的是互斥访问问题同一时刻只能有一个线程进入同步块。在JDK 6之后synchronized经历了锁升级过程无锁-偏向锁-轻量级锁-重量级锁。笔试中如果你能答出这个升级链路会明显加分。CASCompare And Swap是无锁算法的基础核心是比较内存值是否为预期值如果是则更新为新值否则就重试。CAS避免了线程上下文切换的开销但存在ABA问题——解决方式是加版本号Java中对应的类是AtomicStampedReference。9.2 线程池参数配置与拒绝策略线程池是实际开发中使用频率非常高的组件也是搜狐笔试中的加分考点。核心参数有7个corePoolSize核心线程数即使线程空闲也不会被回收。maximumPoolSize最大线程数。keepAliveTime非核心线程空闲存活时间。workQueue任务队列。threadFactory线程工厂。handler拒绝策略。拒绝策略有四种AbortPolicy抛异常、CallerRunsPolicy调用者线程执行任务、DiscardPolicy丢弃任务不报错、DiscardOldestPolicy丢弃队列最老的任务。笔试中如果问线程池满了任务怎么办需要区分队列已满和线程数已达到最大值两种场景来回答。9.3 生产环境中的死锁排查思路虽然死锁的原理在操作系统章节已经提过但笔试和面试中更常考的是如何在生产环境排查死锁。常用的排查步骤使用jps或ps -ef找到Java进程ID。使用jstack pid导出线程栈信息搜索deadlock关键字。查看Found one Java-level deadlock输出确认锁的持有与等待关系。用jvisualvm或jconsole可视化查看线程状态与锁信息。修复方法是调整代码中的锁获取顺序或者使用带超时的锁获取机制如tryLock。这套排查思路对面试官来说非常加分说明你不只是会背概念而是真正在线上环境中遇到过、解决过问题。10. 这套题做完后的几点真实感悟刷完搜狐这套2016年研发工程师笔试题最大的收获不是背会了多少答案而是建立了一套基础知识点变式训练工程思维的复习框架。有几个心得一直用到现在第一经典的常考题目值得反复刷但刷的时候要有意识地问自己如果我是出题人我会怎么改这道题。比如单链表反转可以改成区间反转或K个一组反转二叉树遍历可以改成按之字形打印或序列化与反序列化。每道题都想过变式之后应试能力才会真正提升。第二笔试答题时代码的可读性和健壮性比算出正确答案重要得多。阅卷人每天看几百份卷子看到注释清晰、命名规范、边界条件完整的代码印象分会高很多。我至今保留着一个习惯每道编程题都写函数头注释和时间复杂度标注这在多个公司的笔试中都帮到了我。第三这份机试题里最容易被忽视的是网络和数据库部分。很多同学把时间全花在刷LeetCode上结果遇到TCP握手状态名都想不起来遇到索引失效场景一脸茫然。基础原理的复习要均衡不能偏科。最后给正在准备笔试的同学一个可操作的建议拿三天时间每天花两小时把操作系统、计算机网络、数据库的经典高频题过一遍用手写的方式整理成自己的笔记再用两天时间集中刷数据结构与算法的经典题型重点是链表、二叉树、动态规划、字符串处理。保持每天固定刷题和手写代码的节奏到正式笔试时就不会因为手生而丢分。
返回列表