说实话,看到这份“搜狐2017秋招研发工程师笔试试卷(一)”的时候,我第一反应是有点怀念。那几年正好是移动互联网最疯狂的扩张期,搜狐、网易、腾讯这些老牌门户转型内容平台,对研发岗的需求量非常大,笔试题目也相对成体系。这份卷子虽然年份有点久,但里面的考点放在今天看一点都不过时——数据结构、算法、网络、操作系统、数据库,基本覆盖了研发岗笔试的“老三样”,而且难度把握得比较均衡,既有送分题,也有区分度很高的压轴题。
如果你是准备校招的在校生,或者想跳槽但好几年没刷过题的老开发,这套卷子都值得花两个半小时认认真真做一遍。我当年帮学弟学妹辅导笔试题的时候,就经常拿这套题当模拟考,因为它很能反映一家老牌互联网公司对“基础是否扎实”的判断标准。下面我按实际做题的顺序,把整套卷子里几个核心模块逐一拆开,讲清楚每道题背后的考点、解题思路,以及我在实际批改和复盘时发现的高频错误。
1. 试卷整体设计与考点分布
1.1 搜狐秋招笔试的命题思路
搜狐这样的公司出笔试题,思路和 BAT 那种“海量投递、海量筛选”的模式不太一样。它不是纯粹要难倒你,而是想在有限的两个小时里,快速判断你有没有扎实的计算机基础,以及有没有解决实际问题的代码能力。整套卷子大致分三个梯度。
第一梯度是基础送分题,集中在选择题的前半部分,比如“TCP 三次握手的状态变化”“数据库事务的 ACID 特性”“栈和队列的区别”这类概念题,基本是大学课本原话,只要你认真上过课就能选对。第二梯度是中等题,开始涉及一些简单的计算和推导,比如给一个递归函数让你算时间复杂度,或者给一段 SQL 让你判断执行结果,这部分考察的是你能否把知识用起来。第三梯度是压轴题,通常是最后两道编程题,需要你现场设计算法并写出可运行的代码,这部分才是真正拉开差距的地方。
有意思的是,这套卷子对“工程实践”的考察比重比一般公司要高。我印象很深的是里面有一道关于 Linux 文件权限的题,还有一道关于 Git 操作结果的题。这种东西在学校里不会专门教,但实际工作中天天用,搜狐出这类题其实是在暗示:招你进来不是让你做研究,是让你能直接上手干活。
1.2 核心考点覆盖与分值对比
我统计了一下这套卷子的考点分布,大致如下表所示:
| 考点模块 | 题量占比 | 典型题型 | 难度系数 |
|---|---|---|---|
| 数据结构与算法 | 约35% | 链表操作、二叉树遍历、动态规划 | 高 |
| 计算机网络 | 约20% | TCP/UDP、HTTP 状态码、DNS 解析 | 中 |
| 操作系统 | 约15% | 进程调度、死锁、内存管理 | 中 |
| 数据库 | 约15% | SQL 编写、索引原理、事务隔离级别 | 中 |
| Linux/工程工具 | 约10% | 文件权限、Git 操作、Shell 脚本 | 低 |
| 其他(概率、逻辑) | 约5% | 概率计算、逻辑推理 | 低 |
从这张表能看出来,数据结构与算法是绝对的重头戏,这和现在所有大厂的笔试风格是一致的。但搜狐特别的地方在于,它的网络和操作系统题目占比明显高于一些新兴互联网公司,这可能是因为搜狐的服务器端业务多,对工程师的网络基础要求更高。所以如果你打算投这类传统门户转型的互联网公司,计算机网络一定要好好复习,尤其是 TCP 协议那一块,几乎是必考。
我建议你拿到一套笔试题时,别直接就埋头做,先花五分钟把题目整体扫一遍,标出哪些是“稳拿分”的,哪些是需要花时间算的,哪些是可能需要放弃的。这套策略我在后面还会详细讲。
2. 笔试核心题型拆解与解题策略
2.1 数据结构:链表、二叉树、栈与队列
数据结构这块,搜狐特别偏爱链表和二叉树,基本每年必考。2017 年这道卷子里有一道链表题我印象很深:给定一个单链表,要求判断它是否有环,如果有环,找出环的入口节点。
这题考的是快慢指针,也就是 Floyd 判圈算法。思路不复杂:用两个指针,慢指针每次走一步,快指针每次走两步,如果链表有环,两个指针一定会在环里相遇。但很多人做到这里就停了,忘了题目还要求“找出环的入口”。
找入口的关键是一个数学推导。假设链表头到环入口的距离是 a,环入口到相遇点的距离是 b,相遇点继续走到环入口的距离是 c,那么环的长度就是 b + c。慢指针走的距离是 a + b,快指针走的距离是 a + b + n(b + c),其中 n 是快指针在环里绕的圈数。因为快指针走的距离是慢指针的两倍,所以有:
2(a + b) = a + b + n(b + c)
化简之后得到 a = (n - 1)(b + c) + c。这意味着,从链表头到环入口的距离,等于从相遇点继续走到环入口的距离(再加上若干圈环长)。所以当两个指针相遇后,把一个指针移回链表头,另一个保持在相遇点,然后两个指针每次都走一步,它们再次相遇的位置就是环入口。
这个推导不算难,但考场上能写出来的人不多,因为很多人只记住了“快慢指针判断是否有环”,没深究过“怎么找入口”。这也是我常跟学弟学妹说的:刷题不能只背结论,要把推导过程吃透,否则题目一变就懵。
二叉树这边,搜狐考了一道中序遍历的非递归实现,这题其实比递归实现更贴近工程场景,因为递归有栈溢出的风险。标准做法是用一个显式的栈来模拟递归过程:
vector<int> inorderTraversal(TreeNode* root) { vector<int> result; stack<TreeNode*> stk; TreeNode* current = root; while (current != nullptr || !stk.empty()) { while (current != nullptr) { stk.push(current); current = current->left; } current = stk.top(); stk.pop(); result.push_back(current->val); current = current->right; } return result; }这段代码的核心逻辑是“先一路向左压栈,弹栈时访问节点,然后转向右子树”。很多人写错是因为在转向右子树之后,忘记把当前节点置空,导致死循环。这个细节我后面还会强调。
栈和队列的基础题反而比较简单,搜狐主要考它们的应用场景区别。比如“用两个栈实现一个队列”这道经典题,思路是:入队时往栈 A 压,出队时如果栈 B 不为空,直接弹栈 B;如果栈 B 为空,先把栈 A 的所有元素都弹出来压进栈 B,再从栈 B 弹。这样做的摊还复杂度是 O(1),因为每个元素最多被移动两次。这道题能考察你对栈和队列本质特征的理解:栈是先进后出,队列是先进先出,两个“后进先出”叠在一起,通过两次反转就实现了“先进先出”。
2.2 计算机网络:TCP、HTTP 与 DNS 高频考点
搜狐的网络题风格比较务实,不怎么考晦涩的协议细节,而是偏重“实际工作中你真的会用到的”。TCP 三次握手是必考的,但搜狐喜欢变着花样考。比如给你一个客户端的状态序列,问你每一步对应什么状态。
我做了这么多年面试官,发现很多候选人能背出“SYN_SENT、SYN_RCVD、ESTABLISHED”这些状态名,但真让他描述“为什么二次握手不行”就卡住了。这个问题的核心是:TCP 是双向通信的,客户端和服务端各自需要确认对方的收发能力。三次握手实际上是在交换两个独立的“信道确认”:
第一次握手,客户端发送 SYN,服务端收到后确认了“客户端的发送能力”和“服务端的接收能力”都没问题。第二次握手,服务端发送 SYN + ACK,客户端收到后确认了“服务端的发送能力”和“客户端的接收能力”都没问题。到这一步,客户端已经确认了双方的收发能力,但服务端还不知道“客户端的接收能力”是否正常,所以还需要第三次握手,客户端发送 ACK,服务端收到后确认了“客户端的接收能力”正常。只有经过这三次,双方才能在逻辑上确信“我说的话你能听到,你说的话我也能听到”。
HTTP 状态码那块,搜狐考了一个非常实际的场景:用户在浏览器里访问一个不存在的页面,服务器返回什么状态码?答案是 404。但题目喜欢绕个弯,问你“如果这个页面在服务器端因为权限不足无法访问,返回什么?”答案是 403。很多人把 404 和 403 搞混。我的记忆方法很简单:403 是“你有资格问,但没资格看”,404 是“你问的东西压根不存在”。另外,搜狐还挺喜欢考 301 和 302 的区别。301 是永久重定向,搜索引擎会把权重转移到新地址;302 是临时重定向,搜索引擎保留原地址的权重。这个在网站迁移和 SEO 优化中特别重要。
DNS 解析过程也是一个高频考点,搜狐喜欢让你描述“从输入 www.sohu.com 到页面加载,DNS 经历了什么”。完整流程是:先查浏览器缓存,再查操作系统 hosts 文件,然后查本地 DNS 服务器,本地 DNS 服务器会先查自己的缓存,没有的话就向根域名服务器发起迭代查询,根域名服务器会告诉你“com 顶级域服务器的地址”,再去问 com 顶级域服务器,它会告诉你“sohu.com 权威服务器的地址”,最后去问 sohu.com 的权威服务器,拿到 www 这个主机的 IP 地址。
这道题在改卷时我发现一个很有意思的现象:大部分人都知道“递归查询”和“迭代查询”这两个名词,但搞不清楚谁对谁用什么方式。其实记住一条就行:主机到本地 DNS 服务器是递归查询,本地 DNS 服务器到根/顶级/权威服务器是迭代查询。递归的含义是“你替我把事情办完,最后给我结果”,迭代的含义是“你给我指个路,我自己去问下一家”。
2.3 操作系统与数据库:并发控制与索引优化
操作系统这块,搜狐最爱的考点是死锁的四个必要条件:互斥条件、请求与保持条件、不可剥夺条件、循环等待条件。选择题通常给一个场景,问你会不会产生死锁。比如“两个进程各自持有一个资源,同时请求对方持有的资源”,这就是典型的死锁场景。
数据库方面,2017 年这套卷子出了一道关于索引失效的题,非常经典。题目大概是:有一个表 user,字段包括 id、name、age、phone,索引建立在 (name, age) 这个联合索引上,问以下哪些查询会走索引。
答案涉及最左前缀原则。联合索引 (name, age) 相当于先按 name 排序,再按 age 排序。所以查询条件里必须包含 name 字段才会走索引。如果只查 age,索引就用不上。很多人没搞明白这个原理,是因为不理解联合索引底层的 B+ 树结构——它其实是一棵按多个字段依次排序的树,你先得确定第一层排序的字段,才能继续在第二层查找。
那年的压轴 SQL 题是求“每个部门工资最高的员工”。这个需求在实际工作中非常常见,标准解法是用窗口函数:
SELECT department, employee, salary FROM ( SELECT department, employee, salary, RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rn FROM employee_salary ) t WHERE rn = 1;如果你对窗口函数不熟,也可以用传统的方式:先找出每个部门的最高工资,再关联原表:
SELECT e.department, e.employee, e.salary FROM employee_salary e INNER JOIN ( SELECT department, MAX(salary) AS max_salary FROM employee_salary GROUP BY department ) d ON e.department = d.department AND e.salary = d.max_salary;两种写法都能得到正确答案,但如果部门里有两个人工资一样高,第一种写法用 RANK() 会把两个人都查出来,第二种写法也会查出来,行为是一致的。但如果你用的是 ROW_NUMBER() 而不是 RANK(),那就会只保留一个人,这可能不是你想要的结果。这个细节在面试里很加分,因为它体现你对“到底要取几条数据”这件事有清醒的认识。
3. 编程题完整复盘:从读题到 AC 的实战过程
3.1 经典算法题原题还原与思路推演
2017 年这套卷子的第一道编程题是“最长公共子序列”,简称 LCS,是一道无法回避的经典动态规划题。题目描述很直接:给定两个字符串 text1 和 text2,返回它们的最长公共子序列的长度。子序列不要求连续,但必须保持相对顺序。
一看到这种题,你首先得判断出这是个 DP 问题。怎么判断?我有个比较实用的经验:如果题目里出现“最长”“最短”“有多少种”,而且你感觉穷举会非常爆炸,那大概率是 DP。DP 题的第一步不是想转移方程,而是定义状态。LCS 的状态定义是教科书级别的:dp[i][j] 表示 text1 的前 i 个字符和 text2 的前 j 个字符的最长公共子序列长度。
有了定义,转移方程就水到渠成了。当 text1[i-1] == text2[j-1] 时,说明这个字符可以成为公共子序列的一部分,dp[i][j] = dp[i-1][j-1] + 1。当两个字符不相等时,dp[i][j] = max(dp[i-1][j], dp[i][j-1]),意思是从两个方向“继承”较大的那个结果。我建议所有学 DP 的人都把这道题的推导过程自己在草稿纸上走一遍,因为它是很多复杂 DP 题的基础模板。
def longest_common_subsequence(text1: str, text2: str) -> int: m, n = len(text1), len(text2) # dp[i][j] 表示 text1 前 i 个字符和 text2 前 j 个字符的 LCS 长度 dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): if text1[i - 1] == text2[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]这段代码复杂度是 O(mn),无论 m 和 n 多大,你都不可能用更优的渐进复杂度解决这个问题,因为任何算法至少都要把两个字符串扫一遍。但搜狐这道题有个额外要求,就是如果两个字符串的长度都超过 1000,基础版本可能就会内存超限,需要用滚动数组优化,把二维数组压缩成一维。这个优化本质上是因为 dp[i][j] 只依赖 dp[i-1][j-1]、dp[i-1][j] 和 dp[i][j-1],也就是上一行和当前行的数据,所以只需要保存两行就够了。能想到这一步,在笔试里基本就是满分水平。
3.2 第二道编程题:Top K 问题的工程化变体
这套卷子的第二道编程题更有意思,它问的是:给定一个很大的整数数组,找出其中最大的 K 个数。这题如果数组很小,直接排序然后取前 K 个就完了,时间复杂度 O(n log n)。但它特意强调“很大”,就是在暗示你不能全部排序。
回答这道题有两条路线。第一条是维护一个大小为 K 的最小堆,遍历数组,如果当前元素比堆顶大,就弹出堆顶,把当前元素入堆。这样遍历完,堆里存的就是最大的 K 个数,时间复杂度 O(n log K)。当 K 比较小时,这个方案非常高效。
第二条路线是快速选择算法,也就是快速排序的变种。每次选一个 pivot,把数组分成大于 pivot 和小于 pivot 的两部分,然后判断大于 pivot 的那部分数量:如果刚好等于 K,那这就是答案;如果大于 K,继续在大于 pivot 的部分里找;如果小于 K,那说明 pivot 本身和大于它的部分都是答案的一部分,还需要在小于 pivot 的部分里再找剩下的。这个算法的平均时间复杂度是 O(n),是理论最优解。
但我和很多面试官聊过,大家一致认为第二道题真正的得分点不是算法本身,而是边界条件的处理。比如:
- 当 K 等于 0 时,直接返回空数组;
- 当 K 大于数组长度时,应该返回整个数组还是报错;
- 数组里有重复元素时,相同值怎么处理。
这些细节在简洁的代码里很容易被忽略,但恰恰是实际工程中必须考虑的问题。我通常建议写法是先把异常情况全部处理掉,再写主干逻辑,因为这样能让面试官一眼看出你“先想清楚再动手”的工程习惯。
3.3 手写代码时的规范与细节
在笔试中写代码,和你在 IDE 里写代码是完全不同的体验。没有自动补全,没有编译提示,你只能在白板或在线编辑器里凭记忆把代码敲出来。我自己参加笔试和帮人批改卷子的经验是,最影响得分的往往不是算法思路,而是一些看起来很“低级”的错误。
首要是函数签名。搜狐的在线笔试系统通常是自动判题,它要求你实现的方法名、参数类型、返回值类型必须和题目给的模板严格一致。你哪怕逻辑写对了,但函数签名对不上,编译直接失败,一分都没有。所以拿到编程题的第一步,先把模板里的函数签名抄到答题区,确保不跑偏。
其次是缩进和括号。在线判题系统对空白字符的容忍度通常很高,但人眼评分的时候,缩进混乱的代码会非常减分,面试官甚至可能怀疑你是不是真的会写代码。我有个习惯:在纸上写代码时故意把每个大括号单独占一行,左右对齐,这样即使后来改动,也能快速找到对应的括号。
还有一个很容易被忽略的点是变量命名。笔试阅卷时,面试官看你的代码第一眼看到的就是变量名。用 a、b、c 这种命名虽然也能跑,但看起来就像未经思考的草稿;用 left、right、current、maxHeap 这种命名,即使代码有 bug,面试官也更愿意相信你有能力修好它,因为命名体现的是你大脑里的模型是否清晰。
我建议在笔试前,把常见数据结构的操作代码练到“肌肉记忆”的程度。比如单链表的反转、二叉树的前中后序遍历、二分查找、快排、归并排序、堆的插入和删除,这些代码基本是每一场笔试都会出现的零件。你不需要临场想,直接肌肉记忆写出来,然后花更多时间去处理真正的难点。
4. 备考秋招:如何高效刷题与避坑
4.1 应届生备战笔试的时间规划
如果你现在是大三或研二,准备参加下一年的秋招,我建议你按 12 周来规划复习,不要把战线拉太长,也不要指望突击一个月就能搞定。
前 4 周主攻数据结构与算法基础。把数组、链表、栈、队列、哈希表、二叉树、堆、图这 8 种基本数据结构从头到尾梳理一遍,配合 LeetCode 上面“热题 HOT 100”中的简单题,每天 3 道,雷打不动。这阶段不要追求难题,关键是建立“看到题目能判断出用什么数据结构”的本能。比如看到“维护前 K 大元素”就条件反射想到堆,看到“配对/嵌套”就想到栈,看到“索引进退”就想到队列。
中间 4 周刷中等难度题,开始接触动态规划、贪心、回溯、二分、滑动窗口这些常考算法范式。这个阶段我不建议按题号顺序刷,而是按专题刷。今天专攻 DP,明天专攻回溯,刷完一个专题后停下来总结套路。比如 DP 题的套路是“定义状态 -> 找转移方程 -> 确定边界条件 -> 优化空间复杂度”,你总结多了就会发现,大部分 DP 题都逃不出这几个步骤。
最后 4 周进入模拟笔试冲刺阶段。每周挑两套往年的真题,严格按照 2 小时的时限和真实的在线笔试环境来做。做完之后不要只看成绩,要花至少 2 倍于考试的时间来复盘:每道错题是因为知识点不会,还是因为粗心,还是因为时间分配不合理。我见过太多人刷了几百道题,但真正模拟考试时还是栽在时间管理上,前面选择题磨蹭太久,后面编程题没时间写。
4.2 做笔试题时的时间分配策略
我自己比较推荐“三遍法”来应对笔试。第一遍,花 5 到 10 分钟快速浏览所有题目,标注出每道题的类型和难度。第二遍,先做自己最有把握的题,比如基础概念选择题和简单的编程题,确保拿到保底分。第三遍,再回头啃难题。
选择题的时间分配要卡在 40% 以内。哪怕你看完题一点思路都没有,也不要在一道选择题上纠结超过 3 分钟。因为选择题的答案是客观的,蒙一个还有 25% 的正确率,纠结到最后一题也未必能保证对,反而挤占了编程题的时间。
编程题千万不要上来就写代码。先在草稿纸上画出思路:用哪些数据结构、大致的时间复杂度、边界条件是什么。我建议你在草稿纸上把算法思路写出来,哪怕只是几个关键词,远比直接上手敲代码效率高。因为直接敲代码很容易陷入“边写边改”的泥潭,越改越乱。
如果一道编程题 20 分钟还没有完整的思路,果断放弃写暴力解。很多在线判题系统对暴力解的评分是“通过部分测试用例”“超时但有正确输出”,也能拿到一定分数。宁可写一个时间复杂度过高但逻辑正确的暴力解,也不要留白。留白是 0 分和部分分的天壤之别,这个道理在职场上也一样。
4.3 公司真题的深度复盘方法
做真题的价值不在于题本身,而在于通过真题摸清目标公司的出题偏好。我自己复盘真题时会做三件事。
第一,把公司近 3 年的笔试题按考点分类,统计高频考点。比如搜狐的卷子,连续三年都考了链表和二叉树,TCP 状态变化几乎年年见。对这些高频考点,投入加倍时间重点突破,性价比很高。
第二,分析选项里埋的“坑”。出题人喜欢在干扰项里设置“半对半错”的选项,比如“TCP 是面向连接的、可靠的、全双工的传输层协议,但不保证传输顺序”——这个选项前面全对,最后一句是错的,因为 TCP 恰恰“保证传输顺序”。你如果对知识点的记忆是模糊的,很容易被这种选项带走。通过复盘这些选项,你能发现自己对哪些概念的理解是模棱两可的。
第三,把编程题的解法梳理成模板。比如“链表题套路”可以归纳为:是否需要虚拟头节点、是否需要快慢指针、是否需要反转链表;“子串类题目”得想想是用滑动窗口还是前缀和;“树上路径题”大概率要 DFS 加回溯。你每刷一套真题,就顺手把这些模板更新一遍,到了真正笔试的时候,看到题目先往模板上套,能大幅缩短思考时间。
5. 常见问题与避坑指南
5.1 笔试中最容易失分的细节
作为一个帮人批改过很多份笔试卷子的人,我总结出下面这 5 个高频失分点,几乎每一场笔试都会遇到。我把它做成一个速查表,你可以对照着自查:
| 常见问题 | 具体表现 | 解决办法 |
|---|---|---|
| 函数签名不匹配 | 在线判题编译失败,0 分 | 先把模板函数签名抄到答题区,再做改动 |
| 边界条件遗漏 | 空输入、K = 0、长度不足等场景 | 写代码前先枚举边界条件,逐条处理 |
| 堆栈内存超限 | 没有考虑数据规模,直接用 O(n²) 空间 | 先看题目给出的数据范围,再设计算法 |
| 死循环 | 链表题中节点没有及时后移 | 在纸上模拟 3 轮循环,确认指针移动顺序 |
| 选择题过度纠结 | 一道题耗 10 分钟,编程题没时间写 | 每道选择题限时 3 分钟,超时先标记跳过 |
除了这 5 个,还有一个非常隐藏的扣分点:代码注释。有些人喜欢写一堆注释,这其实是好事,但在笔试时要注意别写与题意无关的注释,比如“这段代码是我想了很久才写出来的”这种,不会加分反而会让面试官觉得你不自信。此外,不要在代码里夹杂太多调试输出(print 语句),因为自动判题系统可能不会忽略这些输出,而导致你的结果被判定为错误。
5.2 这套卷子里最容易被翻车的争议题
2017 年这套卷子有一道题在当年考生里争议很大:给定一个包含 n 个数的数组,找出数组中所有出现次数超过 n/3 的元素。这题如果不会 Boyer-Moore 投票算法变体,很多人第一反应是用哈希表计数,时间复杂度 O(n)、空间复杂度 O(n)。这在笔试中一般能得大部分分,但题目如果加上了“尽可能降低空间复杂度”的条件,你就得用摩尔投票法的推广版本。
推广版本的思路是:出现次数超过 n/3 的元素最多只有 2 个,所以我们可以维护两个候选元素和两个计数器。遍历数组,对于当前元素,如果它等于候选一,候选一计数加一;否则如果等于候选二,候选二计数加一;否则如果候选一的计数为 0,把当前元素设为候选一;否则如果候选二的计数为 0,把当前元素设为候选二;否则候选一和候选二的计数同时减一。遍历结束后,再遍历一遍数组,统计两个候选元素实际出现的次数,验证是否真的超过 n/3。
这个算法的精妙之处在于它用“抵消”的思想代替了哈希表的额外空间,是“空间换时间”思路的逆向版本。笔试时如果题目没明确要求,用哈希表完全没有问题;但如果题目问了“能否用 O(1) 空间”,你写出摩尔投票法就是妥妥的加分项。这提醒我们刷题时同一个题目尝试多种解法,并且理解它们之间的取舍关系,面试时才会更从容。
5.3 复盘工具与参考资料推荐
刷笔试题这件事,光靠自己埋头苦干效率很低,我建议配合下面这些工具和资料来复盘,都是我自己用下来比较靠谱的,不存在广告,纯分享。
在线刷题平台推荐 LeetCode 和牛客网,前者偏算法,后者偏公司真题。如果你在备战秋招,每天我会先花半小时在 LeetCode 上做 1 道中等题保持手感,再花半小时在牛客网上刷目标公司的真题,重点看讨论区里别人的题解思路,尤其是那些“O(1) 空间”“双指针优化”的高赞回答。
资料方面,《剑指 Offer》是入门宝典,题量不大但都是面试高频题;《算法导论》是理论基石,如果你时间充裕想深入理解算法原理可以看,但应急刷题阶段可以放一放;另外还有一本《程序员代码面试指南》是左程云写的,里面的解题套路非常适合国内互联网公司的笔试风格。
如果遇到一道题你怎么都想不明白,可以试试“费曼学习法”:把这道题的解法用自己的话讲给一个不懂编程的朋友听,如果他能听懂,你就真的会了;如果讲着讲着自己卡住了,说明你还有知识盲区,需要回头看书。这个方法听着玄乎,但我实测效果特别好,因为很多知识你以为自己会了,其实只是眼熟了。
写在最后:这套题告诉我们的三件事
回到开头那份 2017 年的搜狐笔试试卷,我认真做过、也认真讲解过,即使过了这么多年,我依然觉得它是一套质量很高的题。它没有故意刁难人,但每一道题都在默默筛选“基础扎实 + 思维严谨 + 有工程意识”的人。所谓的“高分选手”,往往不是天赋异禀的那种,而是那些愿意把基础知识反复打磨、把每道错题彻底弄懂的人。
如果你现在正处于备战校招的阶段,我想分享一个我自己的体会:不要太在意某一场笔试的得失。笔试不过是求职路上的一道门槛,它检验的是你过去的积累,而积累是可以靠每一天的刻意练习来改变的。今天做错一道题,仔细弄懂它,明天你就会在类似的题目上少花 5 分钟。日拱一卒,功不唐捐,把这套卷子拆透了,你的信心也会跟着长起来。