搜狗2020校招后端笔试第一场,是我在帮几届学弟学妹准备校招时反复拿出来讲的一套题。它不像有些厂的笔试那样剑走偏锋出偏题怪题,相反,这套题的考点非常典型:语言基础、网络协议、操作系统、数据结构和算法,再穿插一两道跟搜索引擎业务沾边的设计题。如果你准备的是后端岗位,把这套题吃透,基本等于把大厂后端笔试的常见题型过了一遍。
先说结论:搜狗这套后端笔试题的难度,在当时的一线互联网公司里不算最变态的,但它的覆盖面很全,而且带着明显的搜索引擎业务色彩。全文我会按题型结构、选择题考点、编程题推导、开放题思路、备考复盘这条线来讲,中间会穿插很多我当时实际踩过的坑和总结出来的答题策略。
1. 第一场笔试的全貌:题量、时长与考察范围
1.1 题型分布与时间压力
搜狗2020校招后端笔试第一场,从大家考后复盘的情况看,整体结构是一套选择题加三道编程题,总时长大约120分钟。选择题大概20道左右,包含单选和多选,覆盖C/C++、Java、数据结构、算法、计算机网络、操作系统、数据库和Linux常用命令。分值上选择题和编程题大概各占半壁江山,编程题虽然只有三道,但每道都是硬骨头,直接影响你能不能进面试。
时间压力是真实存在的。20道选择题看着不多,但不少题目本身有迷惑性,比如多选里的“下列说法正确的是”,四个选项里可能有两三个都长得像对的,如果不控制节奏,很容易在前面耗掉四五十分钟。我的建议是:选择题平均每题控制在1.5到2分钟以内,遇到卡壳的先标记跳过,不要在单个题上较劲。
先给个大概的参考时间分配表,具体可以按自己的强弱项微调:
| 题目类型 | 题量 | 建议用时 | 核心策略 |
|---|---|---|---|
| 选择题 | 约20道 | 30-40分钟 | 快准狠,犹豫的题先标记 |
| 编程题第一题 | 1道 | 20-25分钟 | 求稳,拿满分为目标 |
| 编程题第二题 | 1道 | 25-30分钟 | 中等难度,写清楚思路 |
| 编程题第三题 | 1道 | 30-40分钟 | 能拿部分分就部分分 |
| 检查与提交 | - | 10-15分钟 | 检查输入输出边界,别留空题 |
1.2 业务背景如何影响出题
搜狗做搜索起家,后来又做了输入法、AI硬件这些方向,但搜索相关的技术栈始终是它的底色。这套后端笔试题里,你能明显感觉到出题人对文本处理、海量数据统计、检索和缓存这几个方向的偏爱。
具体来说,编程题里几乎必然会有一道字符串处理相关的问题,比如解析搜索日志、统计词频、找出TopK之类的。这不是巧合,而是搜索后端日常工作的高度浓缩。搜索引擎每天要处理海量query,怎么在内存里快速统计、怎么在分布式环境下做归并,都是后端工程师真正要面对的问题。
如果你提前了解这一层背景,备考方向就会清晰很多。不要只闷头刷LeetCode上的纯算法题,还要刻意练一练带业务场景的题目,比如“给一个日志文件,统计每个用户的请求次数并排序输出”。这类题在LeetCode上不一定有原题,但在搜狗、百度这类搜索公司的笔试里出现频率极高。
1.3 笔试平台与提交环境
搜狗那几年校招笔试大多在牛客网上进行,选择题和编程题在一套试卷里连续作答,编程题采用ACM模式,也就是需要你自己处理标准输入输出,而不是像力扣那样只需要实现一个函数。这个区别非常重要,因为很多人平时刷题刷习惯了,只写核心函数,到了笔试平台上一遇到输入解析就手忙脚乱。
我记得当时辅导过的一个学弟,第一道编程题逻辑完全写对了,但读取一行字符串时没处理行尾换行符,导致结果对不上测试样例,白白浪费了将近二十分钟排查。这个问题在本地IDE里通常看不出来,因为本地输入不会带那些隐藏的换行符。我的经验是:笔试前一定要去牛客网上做几套模拟题,适应一下它那种“全篇代码自己写main、自己解析”的形式。
另一个容易被忽略的点是:牛客网的提交是单题分别判分的,三道题之间可以任意顺序切换到下一题。如果你在第三题上卡死,完全可以先回头把第一题第二题的边界情况再检查一遍,把能拿的分拿稳,而不是死磕一道题到最后一刻。
2. 选择题里的高频考点:网络、操作系统与语言基础
2.1 C/C++与Java语言基础的考察角度
搜狗后端的主要技术栈是C++和Java,所以语言基础选择题也基本围绕这两种语言展开。C++这边,虚函数、构造与析构顺序、智能指针、内存管理是绝对重点;Java那边则是JVM内存区域、垃圾回收、集合类底层实现、并发包这些。
举个例子,有一类题特别经典:给定一个类B继承自类A,类A里有一个成员对象C,问创建B对象时构造函数的调用顺序。这种题没有任何技巧,纯粹看基础扎不扎实。正确的是先构造基类A,再构造成员对象C,最后执行B自己的构造函数;析构顺序完全相反。但很多人会栽在“成员对象的构造顺序取决于声明顺序,而不是初始化列表里的顺序”这个细节上。
还有Java的HashMap,当时Java 8的升级已经把链表转红黑树的阈值、扩容机制这些讲得很细了,笔试里也很喜欢考。比如问你“HashMap什么时候会触发树化”“为什么链表转红黑树的阈值是8”,这类题本质上是在考察你对底层实现有没有真正读过源码,而不是只知道会用。
2.2 TCP、HTTP与网络模型经典问法
网络协议是后端笔试选择题里性价比最高的一块,因为考点非常固定。TCP三次握手、四次挥手、为什么挥手要四次、TIME_WAIT状态的作用、SYN Flood攻击的原理、HTTP状态码的含义、GET和POST的区别、Cookie和Session的关系,基本上翻来覆去就这些。
搜狗这套题里,我记得比较深的是考了一道关于TIME_WAIT的题:主动关闭连接的一方在收到对方的FIN后会进入TIME_WAIT状态,为什么这个状态要等2MSL而不是直接关闭。正确理解是:一方面要确保自己最后发的ACK能被对方收到,如果丢了对方会重发FIN;另一方面是为了让旧连接上的延迟报文段在网络中消失,避免影响相同四元组的新连接。这个点如果只是背答案,很容易在多选变体里翻车。
这里给个提醒:网络题不要只背结论,要能自己画一遍时序图、解释清楚每个状态迁移的原因。面试现场画图倒不至于,但笔试选择题里多选变体非常多,只有理解了原理才能准确判断哪些选项是对的,哪些是出题人故意挖坑的。
2.3 操作系统、数据库与Linux命令选择题
操作系统这边,死锁产生的四个必要条件、进程和线程的区别、虚拟内存和页面置换算法、进程调度算法,都是高频考点。LRU缓存淘汰策略在选择题里出现的频率尤其高,因为它既能考操作系统里的页面置换,又能考Redis内存淘汰,还能和编程题里的缓存设计联动。
数据库的选择题主要集中在索引和事务上。B+树为什么适合做数据库索引、什么情况下索引会失效(比如对索引列使用函数、隐式类型转换、左模糊查询)、事务的ACID特性、四种隔离级别分别解决什么问题,这些都属于后端笔试“必考清单”。特别是隔离级别和“脏读、不可重复读、幻读”的对应关系,几乎每个厂都考。
Linux命令的选择题一般是送分题,但也会出一些容易混淆的选项。比如查看进程用ps,查看端口占用用netstat或ss,查看磁盘用df,查看内存用free,查看日志用tail和grep组合。不要小看这些命令,有时选择题里会故意把netstat和ss搞混,或者把df和du的用途互换,基础不牢的人很容易凭印象选错。
3. 编程题实战:三道算法题的完整推导
3.1 字符串与模拟题:别在小细节上翻车
编程题第一道通常是整场考试的热身题,难度不大,但特别考验细致程度。搜狗这套题里,第一道我印象里是给一段搜索日志,每行格式类似“用户ID\t搜索词\t时间戳”,要求统计每个用户的总搜索次数,按次数降序输出,次数相同的按用户ID升序。
这道题考察的核心就是字符串切分、哈希统计和排序,思路三秒钟就能想出来,但真正写起来有几个细节很容易踩坑:
- 输入行数不固定,需要用while循环读取到文件末尾,不要假设固定行数。
- 分隔符是制表符\t,不是空格,如果用split(" ")去切会得到一堆空串。
- 用户ID可能包含字母和数字,排序时按字典序,不是按数值序。
- 输出格式要跟题目要求完全一致,多一个空格都可能导致判分失败。
一个简化版的参考逻辑是这样:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); Map<String, Integer> countMap = new HashMap<>(); String line; while ((line = reader.readLine()) != null && line.length() > 0) { String[] parts = line.split("\\t"); if (parts.length >= 2) { countMap.put(parts[0], countMap.getOrDefault(parts[0], 0) + 1); } } countMap.entrySet().stream() .sorted(Map.Entry.<String, Integer>comparingByValue().reversed() .thenComparing(Map.Entry.comparingByKey())) .forEach(e -> System.out.println(e.getKey() + " " + e.getValue()));注意,字符串切分时我在split里写了\\t而不是\t,因为Java的split接收的是正则表达式,裸的\t会被当成转义字符本身,只有写成\\t才能匹配制表符。这种细节在本地IDE里可能因为输入样本恰好没体现出来而被忽略,但在判题机的隐藏用例里一测一个错。
3.2 TopK与词频统计:搜索引擎的常客
第二道编程题通常开始上强度了。搜狗这套里,有一道是给一篇很长的英文文本,要求找出出现频率最高的K个单词,如果频率相同按字典序输出。这题本质上就是TopK问题,在搜索引擎的后端业务里非常常见,比如统计热门搜索词、找出热门新闻关键词,都是同一个套路。
我当时给学弟学妹讲这道题时,强调了一个关键决策:为什么要用小顶堆而不是把全部数据排序?
假设文本里一共有N个不同单词,如果全部排序,时间复杂度是O(N log N);如果维护一个大小为K的小顶堆,每来一个单词就与堆顶比较,比堆顶大就替换并调整,最终堆里留下的就是最大的K个,时间复杂度只有O(N log K)。当N是百万级、K是10的时候,这个差距是数量级的。
另一个隐藏的细节是如何统计单词。如果对每个单词都调用一次containsKey加put,性能其实也够,但更优雅的写法是:
Map<String, Integer> freq = new HashMap<>(); for (String word : words) { freq.merge(word, 1, Integer::sum); } PriorityQueue<Map.Entry<String, Integer>> heap = new PriorityQueue<>( (a, b) -> a.getValue().equals(b.getValue()) ? b.getKey().compareTo(a.getKey()) : a.getValue() - b.getValue() ); for (Map.Entry<String, Integer> entry : freq.entrySet()) { heap.offer(entry); if (heap.size() > K) { heap.poll(); } }堆顶是当前K个里频率最小的,频率相同时字典序最大的先被淘汰,这样最后堆里剩下的就是频率最大、字典序最小的K个。很多人会把比较器的方向写反,我的经验是:写完比较器之后,先用一个只有三五个元素的小例子手动推一遍,确认堆顶是符合预期“最该被淘汰”的那个,再提交。
3.3 动态规划题:拉开区分度的关卡
第三道编程题一般是动态规划或者带一点算法设计的题。按2020年前后搜狗的出题风格,我复盘到的是一道类似于“最小编辑距离”的题:给定两个字符串,允许插入、删除、替换,问最少多少次操作能把第一个串变成第二个串。这道题当年在牛客上讨论热度很高,因为DP状态转移很经典,但边界处理容易出问题。
状态定义是dp[i][j]表示字符串A的前i个字符变成字符串B的前j个字符所需的最小编辑次数。转移方程分两种情况:
- 如果A[i-1] == B[j-1],那么
dp[i][j] = dp[i-1][j-1],不需要额外操作。 - 如果不相等,取三种操作的最小值再加1:
- 替换:
dp[i-1][j-1] + 1 - 删除:
dp[i-1][j] + 1 - 插入:
dp[i][j-1] + 1
- 替换:
初始化时,dp[i][0] = i表示把A的前i个字符全部删掉,dp[0][j] = j表示空串插入j个字符。这个初始化很多人会漏,导致第一行第一列全部是0,结果全错。
如果只求最优值,可以用滚动数组把空间从O(mn)优化到O(n),因为每一行的状态只依赖上一行和当前行。笔试题里空间优化一般不是必须的,但写出来会是个加分项。我当时鼓励学弟学妹用滚动数组,因为面试官看笔试代码时,看到你能主动优化空间,印象分会高一些。
另外,这类DP题的输入是两个字符串,要注意字符串里是否包含空格,如果用next()读就只会读到空格前的部分,导致后续字符全部丢失。正确做法是用nextLine()或按行读取后去掉末尾换行。
3.4 笔试现场踩过的坑:输入输出、越界与心态
编程题部分的坑,很多不是算法本身的坑,而是工程习惯的坑。我把当时复盘时总结的常见问题列在这里,都是真实发生过的事:
- 输入读取提速:不要用Scanner逐行读大批量数据,用
BufferedReader加StringTokenizer或者直接split,效率差好几倍。有些判题机数据量大,Scanner读超时是真的会发生的。 - 数组越界:DP题里最容易出现
dp[i-1]没判断i是否大于0,或者循环从0开始时访问到dp[-1]。我的习惯是循环下标从1开始,把下标0的位置留作边界初始化。 - 整数溢出:统计词频、计算路径数这类题,结果可能超过int范围,该用long就早用long,不要等溢出了再回头改类型。
- 输出格式:题目要求输出以空格分隔还是换行分隔、末尾是否允许有多余空格,这些都必须严格照做。判题机是按字符串精确比对,不是人眼阅卷。
- 找不到bug时重读一遍题:有至少20%的情况,代码逻辑没错,是你看漏了题目的某个限制条件,比如“单词不区分大小写”“只考虑字母字符”等隐含条件。
心态上,我记得有不少人第一道题因为小细节卡了半小时,直接心态崩了,后面两道题草草收场。我的建议是:如果一道题连续调试超过15分钟还没找到问题,先把它放一放,去做下一道,等整个试卷都过完一遍再回来。大脑切换上下文之后,往往一眼就能看出之前忽略的问题。
4. 开放性问题:搜索引擎场景下的后端设计题
4.1 这类题为什么会出现
搜狗后端笔试里,偶尔会有1到2道简答或设计类题目,不需要写出完整代码,而是让你用文字描述设计方案。很多人准备笔试时完全不练这类题,结果考场上遇到只能随便写两句。其实这类题恰恰是最能体现后端工程师和纯刷题选手差异的地方。
搜索引擎公司为什么要考设计题?因为后端日常工作里,很多问题不是“写一个算法”能解决的,而是要综合考虑数据量、并发、缓存、存储、容错。笔试里考察你一下字符串处理或TopK,只能看出你代码写得好不好;考一道设计题,才能看出你有没有做工程的sense。
4.2 典型设计题:搜索框关键词联想
我当时复盘到的一道代表性设计题是:请设计一个搜索框的关键词联想服务,用户输入“北”时,下拉框要能展示“北京”“北京大学”“北京天气”等热门词,要求说明数据结构、存储方案和更新策略。
这道题的经典解法分两层。第一层是前缀匹配的数据结构,用Trie树(字典树)存所有候选词,每个节点记录经过该前缀的热门词TopK列表。这样用户每输入一个字符,就能立刻在Trie树上走一步,然后返回当前节点预存好的TopK,时间几乎只跟K有关,跟词典大小无关。
第二层是工程方案的取舍。不可能把所有词和频次都存在内存里,更不可能实时去数据库里数一遍频次,所以要做离线计算加在线召回:离线统计过去一段时间的热门搜索词,定期构建或更新Trie树及每个前缀的TopK列表,推到Redis等缓存中;在线服务收到请求时优先查本地缓存,查不到再查Redis,Redis也没有就退回一个较小的默认词表。
我在讲这道题时,一定会强调容量估算。比如假设候选词有1亿条,每条平均长度20字节,加上Trie树的指针开销,原始Trie树可能需要几GB到十几GB内存,这样的量级单机是能扛住的,但要考虑多副本和容灾。答出这一层,说明你真的思考过系统的成本问题,而不仅仅是在背架构。
4.3 另一个方向:接口性能优化
还有一种开放题风格是给场景让你优化。比如“搜索结果页接口很慢,用户普遍反馈卡顿,你作为后端工程师会怎么排查和优化”。这种题没有标准答案,但有一个很固定的答题框架,我习惯称之为“先定位,再拆分,后优化”。
先定位:用链路追踪或日志分析找出慢在哪一环。可能是数据库查询慢、可能是下游服务响应慢、可能是代码里有耗时操作,甚至可能是网络抖动。没有数据支撑的优化都是瞎猜。
再拆分:从浏览器发起请求到页面渲染,整个链路上有哪些环节。前端发了几个请求、网关有没有做聚合、后端服务有没有串行调用可以改成并行、数据库有没有慢查询和全表扫描、有没有大量重复查询可以加缓存。
后优化:按照性价比从高到低排列优化手段。加缓存、加索引、改SQL、并行化调用、异步化非核心逻辑、限制单用户QPS、做降级方案。每一层优化都要能验证效果,比如对比优化前后的P99耗时。
开放性问题的答题要点是结构清晰、步骤完整、可行性高。面试官和判卷人不会期待你设计出一个完美的系统,他们想看的是你有没有一套稳定的分析框架,能不能把一个大问题拆成小问题逐个击破。
5. 从笔试题到后端日常工作:复盘后的三点体会
5.1 考点其实都在映射真实业务
我把搜狗这套笔试题的考点和后端日常工作做了个对应,发现重合度非常高。TopK词频统计对应的是热门搜索词挖掘,字符串解析对应的是日志清洗,最小编辑距离对应的是搜索词纠错,关键词联想设计对应的是搜索补全服务。笔试不是在考你偏题怪题,而是把你放进一个准后端工程师的角色里,看你能不能处理真实工作中会遇到的建模需求。
想通这一点,备考的思路就不一样了。刷题时多问自己一句“这道题在实际业务里可能出现在哪里”,而不是机械地记套路。带着业务视角去刷题,效果远比盲目刷题数量要好得多。
5.2 针对搜索类公司的备考节奏建议
如果你瞄准的是搜狗、百度这类搜索业务为主的公司,备考节奏可以这样安排:先用两周时间把数据结构与算法的基础模块过一遍,重点放在字符串、哈希表、堆、Trie树、并查集、DP这几块;然后开始刷往年真题和牛客上的模拟卷,每天至少完整做一套,严格计时,模拟真实笔试环境。选择题丢分多的模块,单独拉出来做专项训练,比如网络协议题错得多,就把TCP/IP那几章重新精读一遍。编程题则坚持“每题两种解法”的原则,想出一种解法后别急着写,先思考一下有没有更优的数据结构或更省空间的方案,再动键盘。
考前一周,不要再刷新题了。把那段时间整理的错题本拿出来反复看,重点看那些“以为会但做错”的题。笔试考场上真正拉开差距的,往往不是最难的题,而是容易题里的细节你能不能一次做对。
5.3 一个实用的错题复盘方法
最后分享一个我自己的复盘习惯。每次笔试或者模拟测试结束后,我会上网找同场考生的讨论帖和题解,然后建一张表格,把每道错题的几个关键信息记录下来:题目描述、我的错解想法、错在哪里、正解思路、同类题变体。不要只在脑子里过一遍,一定要写下来,而且要写得足够具体。
这张表积累到二三十道题之后,你会发现自己的错误类型非常集中。有的人总是栽在数组越界,有的人总是在多选里过度纠结,有的人喜欢在字符串处理上用错误的切分方式。考前复盘时只盯着这些高频错误点看,效率是最高的。我当时靠这个习惯,把笔试里的失误率从最开始的一道题错一次,降到了后期的基本稳定,这也算是我校招季最值得做的一件事。
如果你也在准备后端校招,建议你也把这个习惯保留下来,尤其是多花点时间研究透一套像搜狗这样考点覆盖全面的真题,远比盲目刷几十套雷同的卷子更有价值。