2017年秋天,我坐在校招笔试的机房里,屏幕上是“58同城2017秋招研发工程师笔试试卷”。三个小时,选择题、问答题、编程题一整屏铺下来,考完只有一个感受:这家公司的笔试不玩虚的,全是基础功,尤其把数据结构和算法放在了绝对核心的位置。
现在回头看,这份试卷的参考价值比想象中大得多。它不只是一次招聘筛选,更是一份浓缩的考点地图,几乎把校招笔试最可能考的计算机基础、数据结构、算法、网络、数据库、Linux都过了一遍。对准备校招的应届生来说,把这套题的考察逻辑吃透,比盲目刷几十套押题都管用。下面我就以这份试卷为线索,结合我自己当年参加笔试、后来也参与过校招技术面试的经验,把里面的门道掰开揉碎讲清楚。
1. 先看懂这份试卷:结构与定位
1.1 那一年互联网校招笔试的大背景
2017年秋招正值移动互联网高速扩张期,58同城和赶集网合并后业务盘子很大,研发岗位的简历量相当可观。公司要在短时间内从大量候选人里筛出基础扎实、代码能力过关的人,笔试就成了成本最低的第一道关卡,通过率通常不到20%。
我当时观察到一个非常明显的现象:头部互联网公司的笔试风格高度趋同。选择题考基础,问答题考理解,编程题考动手,这套结构在58同城以及其他一线大厂里几乎是通用模板。所以这份2017年的试卷,即使放到今天,依然能作为校招笔试的“标准样本”来练手。你不需要去猜今年会考什么,因为核心考点从来就没变过,变的只是包装题目的场景外衣。
1.2 题型分布与时间分配
从这套卷子呈现出来的结构看,整体可以分成四类题型,每类对应不同的能力维度。
| 题型 | 大致题量 | 建议用时 | 考察重点 |
|---|---|---|---|
| 单选题 | 20题左右 | 30分钟 | 基础知识速答,覆盖网络、OS、语言特性 |
| 多选题 | 10题左右 | 20分钟 | 概念细节辨析,容易漏选错选 |
| 问答题 | 2-3题 | 40分钟 | 场景设计、方案比较、原理阐述 |
| 编程题 | 2-3题 | 剩余时间 | 数据结构和算法的实际实现 |
这里我要特别说一句:时间分配是笔试里最容易被低估的坑。很多人死在编程题上,不是因为不会写,而是前面选择题纠结太久。我的建议是,选择题遇到拿不准的,先按第一感觉选,标记一下,立刻跳到下一题。编程题是绝对的大头,一道AC的题可能顶五道选择题的分数,这个账必须算清楚。
2. 数据结构与算法:笔试的绝对主战场
2.1 必考的排序与查找,不只是背复杂度
这套试卷的选择题和编程题里,排序和查找是出场率最高的两个知识点。常见考法有三种:给一组数据问你快排第一趟后的序列、比较不同排序算法在特定场景下的优劣、手写二分查找并处理边界条件。
很多同学背得住“快排平均O(n log n)”,但一看到“在近乎有序的数组上快排性能退化”就懵了。这里的关键是理解快排的退化原因:每次选择的基准值都落在端点附近,导致分治不平衡,递归深度退化为O(n)。所以实际笔试中,你要能说出来优化手段——随机选基准、三数取中、小规模数据切换到插入排序。我当年就遇到过一道场景题,要求在一个已经基本有序的大数组上做排序,标准答案反而不是快排,而是插入排序的改进版,因为插入排序对局部有序数据的实际开销接近O(n)。
查找方面,二分查找的代码虽然短,但边界条件是经典失分点。后面我在编程题部分会给出一个可以直接默写的模板。
2.2 链表、树和哈希,三种高频结构的出题套路
链表的笔试考法非常固定:反转链表、判断环、找中间节点、合并两个有序链表。这些题看着简单,但限时手写仍然能拉开差距,因为链表指针操作最容易出现空指针异常和死循环。树的高频考点则是前中后序遍历、层序遍历、二叉树深度、最近公共祖先。哈希的高频考点集中在HashMap的实现原理、哈希冲突处理方式以及扩容机制。
这里我要补充一个容易被忽略的细节:2017年出题时,Java 8的HashMap已经开始普及,所以题目里“链表转红黑树”的阈值问题偶尔会出现。即使你用的是C++写笔试,也要能说清楚这个概念,因为这是选择题里的经典选项。
2.3 经典算法题解析:从暴力到最优解
算法题里最常考的还是动态规划、贪心、DFS/BFS这几个大类。我拿一个“最大子序和”来举例,因为这是58这类笔试中出现频率极高的原型题。
题目描述很简单:给定一个整数数组,找到一个具有最大和的连续子数组,返回其最大和。
最暴力的做法是三重循环枚举左右端点并求和,时间复杂度O(n^3),笔试里这种答案肯定是0分。稍微优化可以做前缀和,变成O(n^2),依然不够看。标准解法是动态规划,状态转移方程是 dp[i] = max(dp[i-1] + nums[i], nums[i]),最终答案就是dp数组里的最大值。
public int maxSubArray(int[] nums) { int cur = 0; int max = Integer.MIN_VALUE; for (int num : nums) { cur = Math.max(cur + num, num); max = Math.max(max, cur); } return max; }这段代码的核心思路是:如果前面的累加和对当前元素没有增益,那不如从当前元素重新开始。空间复杂度从O(n)进一步优化到O(1),因为dp[i]只依赖dp[i-1]。我在实际笔试中习惯直接写这个滚动版本,既节省时间又不容易出错。
3. 计算机基础:送分题与送命题之间
3.1 计算机网络:三次握手、四次挥手、HTTP状态码
计算机网络在58这套笔试里占比不低,尤其是TCP和HTTP相关的题目。三次握手为什么是三次而不是两次,四次挥手为什么是四次而不是三次,这两个问题几乎是必考的。关键点在于:握手需要确认双方的收发能力,而挥手因为TCP是全双工的,每一方向关闭连接都需要一次FIN和一次ACK。
HTTP状态码也是选择题常客。200、301、302、403、404、500、502、503这几个是必须条件反射级别记住的。这里有个容易混淆的点:301和302的区别。301是永久重定向,302是临时重定向,这在问答题里如果涉及接口迁移设计,就是一个考察点。还有503 Service Unavailable表示服务器暂时无法处理请求,很多人会和500 Internal Server Error搞混,实际上500是服务器代码出错,503通常是过载或维护导致。
3.2 操作系统:进程线程、死锁、内存管理
操作系统的高频考点非常集中:进程和线程的区别、死锁产生的四个必要条件、虚拟内存和页面置换算法、用户态和内核态的切换。
死锁四条件——互斥、持有并等待、不可剥夺、循环等待——是问答题的标准素材。出题人通常会给一个实际场景,比如两个线程分别持有锁A和锁B,然后互相等待对方的锁,问你如何避免。通用的答法是打破四个条件中的任意一个,实际工程里最常见的做法是破坏“循环等待”,也就是所有线程都按照相同的顺序加锁。这个思路在编程题问答题里可以直接套用。
3.3 数据库:SQL、索引与事务
数据库知识在研发岗笔试里主要考三块:SQL的增删改查、索引的数据结构与失效场景、事务的ACID特性。
SQL题一般给你两张表,要求写出某个查询,重点考察JOIN、GROUP BY、HAVING的组合使用。索引部分必考B+树,你要说清楚为什么用B+树而不是B树或红黑树:B+树非叶子节点不存数据,单节点能存储更多索引项,树的高度更低,磁盘IO更少;同时叶子节点用链表串联,非常适合范围查询。索引失效的场景也常考,比如对索引列使用函数、隐式类型转换、like以通配符开头,这些都会让索引无法使用。
4. 编程题实战:从读题到AC的全过程
4.1 编程题的常见题型与解题模板
58这套笔试试卷的编程题,整体难度属于中等偏上,通常最后一题会拉开差距。常见题型有三类:第一类是纯数据结构的操作,比如链表反转、二叉树遍历;第二类是经典算法应用,比如动态规划、DFS回溯;第三类是场景模拟题,给一段业务描述,让你用代码实现某个逻辑。第三类最容易失分,因为需要从冗长的描述中提取出真正的数据结构和算法模型。
我建议你在笔试前把几个核心模板默写到肌肉记忆:二分查找边界处理、二叉树前中后序遍历的递归和迭代写法、DFS回溯的剪枝框架、LRU缓存的LinkedHashMap实现。这些模板在考场上能帮你节省大量思考时间,把精力留给真正需要设计的部分。
4.2 一道典型编程题的完整解法演示
我挑一道这道试卷里很容易出现的原型题:反转链表。要求输入1->2->3->4->5,输出5->4->3->2->1。
这道题看似基础,但限时手写时,很多人在指针切断和重连的顺序上栽跟头。核心思路是维护三个指针:prev指向前一个节点,curr指向当前节点,next临时保存下一个节点。每一步先把next保存下来,再让curr.next指向prev,最后三个指针整体后移。
public ListNode reverseList(ListNode head) { ListNode prev = null; ListNode curr = head; while (curr != null) { ListNode next = curr.next; curr.next = prev; prev = curr; curr = next; } return prev; }有个面试官问过我怎么验证这段代码是对的。最好的办法是拿一个长度为奇数的链表和一个长度为偶数的链表分别走一遍,检查最后返回的节点是否指向原来的尾节点,同时确认新链表没有环。笔试机考时没有调试器,这种“脑内跑用例”的能力很重要。
4.3 时间复杂度和空间复杂度的把控
编程题评分通常不仅看答案对错,还会看复杂度的优化程度。如果题目要求O(n log n)而你只写出了O(n^2)的解法,即使小数据能跑通,大数据用例也会超时。
我的经验是,在写代码之前先估算数据规模。题目通常会给数组长度n的范围,如果n ≤ 10^4,O(n^2)勉强能过;如果n ≤ 10^5,就必须上O(n log n)甚至O(n)的解法。这道试卷的编程题,多数情况下n都能到10^5级别,所以一个能快速判断复杂度量级的意识,比会背更多算法更重要。遇到题目没有任何提示时,直接按最坏情况估计,然后选择对应复杂度的算法。
5. 备战校招笔试的实操路线
5.1 三个月冲刺计划:分阶段提升
针对这样的试卷结构,我推荐的备战节奏是三个月,分三个阶段。
第一个月打基础,主攻数据结构和计算机基础。数据结构要把数组、链表、栈、队列、哈希表、树、图这七类全部过一遍,能够手写基础操作。计算机基础要把网络、操作系统、数据库的核心概念整理成自己的笔记,每天花一小时背诵和默写。第二个月进入刷题期,每天保证三道编程题,优先刷高频题,重点训练“看到题目能快速判断考点”的能力。第三个月做模拟实战,找完整套题,严格按照考试时间限时完成,重点训练时间分配和心态控制。
5.2 高效刷题的正确姿势:少而精
很多人在刷题时容易掉进一个误区:追求数量,一天刷十道简单题,看起来很努力,实际上进步不大。我推荐的方法是“一类一类地过”。比如这一周只刷链表题,把链表反转、环检测、合并、排序、删除重复节点全部刷透,再进入下一类。这样集中训练能把一类题目的解法内化成思维框架,考试时看到链表题,条件反射就能想到几种常见解法。
刷题时不要直接看题解,先自己想至少20分钟。实在想不出来再看,看明白之后关掉题解,自己重新写一遍。第二天再写一遍,一周后再写一遍,直到能流畅AC才算真正掌握。这个过程看起来很慢,但长期效率远高于题海战术。
5.3 笔试现场的策略与心态
笔试现场最怕的不是题目难,而是节奏崩掉。我在考场上见过太多人,卡在一道选择题上花掉十分钟,结果编程题只剩二十分钟,最后不得不交白卷。正确的策略是:先快速扫一遍所有题目,把简单的标记出来,优先拿稳这些分。选择题和问答题控制在总用时的一半以内,剩下的一半给编程题。
心态方面,碰到完全没思路的编程题,先写一个暴力解拿到部分分,也比什么都不写好。很多线上笔试系统是分用例给分的,暴力解至少能通过小数据用例,这个分数不要白给。记住一个原则:笔试不是竞赛,目标是尽可能多地拿分,不是解出每一道题。
6. 笔试翻车点复盘:这些问题别再犯
6.1 典型技术失误:边界条件与输入处理
我复盘了自己以及身边同学当年在这类笔试里翻车的案例,发现技术上的失误集中在三个地方。第一个是数组越界,尤其是循环中访问i+1或者i-1时没有判断边界。第二个是输入输出的格式问题,机考系统要求严格匹配输出格式,多一个空格都可能被判错,我建议在本地练习时就直接用系统要求的IO模板。第三个是整数溢出,很多题目会涉及大数求和或乘法,结果超出int范围,需要用long处理。
6.2 题目陷阱识别:出题人埋在细节里的坑
这类试卷的出题人非常擅长在细节里埋坑。比如二分查找的数组可能包含重复元素,问你返回第一个出现的位置还是任意位置;链表题的节点可能为空;数组题的数字可能包含负数。这些细节直接影响代码的逻辑分支,读题时一定要养成圈画关键条件的习惯。
多选题的扣分规则也很坑,常见规则是少选得部分分,错选不得分。所以拿不准的选项宁可不选,也不要赌。我看到太多人因为多选了一个错误选项,导致整题零分,这是非常可惜的。
6.3 考后复盘的正确方式
笔试结束后,不管感觉好坏,都要做一次完整的复盘。把每一道错题归类,看看是知识盲区、粗心失误,还是时间不够导致放弃。如果是知识盲区,回到对应的知识点重新学习;如果是粗心,记录下来,下次考试前十分钟专门默读一遍自己的失误清单。
我当时准备了一个“翻车本”,记录了诸如“忘记判空”“循环条件写成<而不是<=”“输出格式少了换行”这类低级错误。每次笔试前翻一遍,能有效降低重复失误的概率。这个习惯我一直保持到参加工作后的技术评审,本质上是同一套思维:从每一次输出结果倒推过程中的问题,然后在下一次迭代中修正。
最后再分享一个我个人的体会:2017年这份试卷里的考点,到今天依然是校招笔试的主旋律。刷题和准备最大的价值,不是押中某道题,而是在反复训练中把基础能力练到条件反射的层面。真正到了考场上,你靠的是肌肉记忆,而不是临时思考。祝每一个认真准备的人,都能拿到理想的offer。