2018年网易校招研发工程师(有道方向)的笔试卷,放到今天回头看,依然值得拿出来仔细拆一遍。它考察的东西非常典型——C/C++与Java语言基础、数据结构、计算机网络、操作系统、数据库,再加上两三道编程题,整体限时两小时。对于目标是研发工程师岗位的同学来说,这套卷子的意义不只是在当年筛人,它还清晰地画出了校招技术笔试的必考点边界。官方虽然没有公布过完整标准答案,但结合我们那批考生考后的复盘,以及我后来在校招季里帮公司出题、看卷的经验,这套题的考察逻辑和答题策略是可以完整还原的。如果你是正在准备大厂研发岗校招的人,或者想系统查漏补缺,这篇文章会把试卷考察的逻辑和你平时容易忽略的细节拆开揉碎,一步步讲清楚。
1. 从试卷结构看有道研发岗的技术要求
1.1 题型分布与各模块占比复盘
网易2018校招研发工程师(有道方向)笔试,我记得采用的是线上笔试方式,总时长两小时。题型分为单选题、多选题和编程题三大类,单选多选覆盖计算机基础知识,编程题聚焦算法与数据结构。不同批次的试卷在具体题目上会有随机变化,但核心考点相对稳定。我根据自己的考试记录和周围同学的反馈,整理了一张大概的知识点权重表,这里分享给大家:
| 知识模块 | 大致题量 | 占选择题比例 | 典型出题方向 |
|---|---|---|---|
| 数据结构与算法 | 6-8题 | 30% | 二叉树、排序、哈希、动态规划 |
| C/C++/Java语言基础 | 4-6题 | 20% | 虚函数、内存布局、集合类 |
| 计算机网络 | 3-5题 | 15% | TCP握手、HTTP、滑动窗口 |
| 操作系统 | 3-5题 | 15% | 死锁、分页、进程线程 |
| 数据库 | 2-4题 | 10% | SQL、索引失效、事务隔离 |
| 概率/组合数学 | 1-3题 | 10% | 排列组合、期望值计算 |
这个比例不是凭空估的,而是考后真题复盘得出的规律。它的核心信息是:数据结构与算法永远是第一大块,语言基础紧随其后,计算机网络和操作系统占比相近,数据库题量虽少但从不缺席。如果你复习时间紧张,应该优先堆算法题量,而不是在冷门知识点上死磕。我见过太多同学把重心放在背诵各类协议细节上,结果算法题没练够,编程题AC不了,这属于本末倒置。
从题目难度来看,选择题大概有60%是基础题,只要复习到位就能拿到分;30%是中等题,需要你做一些简单的计算或推导;剩下10%是拉开区分度的题,通常涉及多个知识点的结合。编程题则一般遵循"一题简单、一题中等、一题偏难"的阶梯分布,入手难度比选择题高不少,但它才是决定你能不能进入面试环节的关键。这里多说一句,很多人忽视选择题,觉得编程题做出来就行,其实网易这类大厂的笔试,选择题的得分会在同岗位候选人之间做横向比较,编程题AC情况差不多的情况下,选择题正确率就成了筛选的关键指标。
1.2 有道方向与其他业务线笔试题的差异
网易当年校招有电商、有道、游戏、云音乐等不同业务线,虽然笔试可能共用一套卷子,但针对不同岗位方向的题目倾向还是有差异的。有道这边最大的特点是看重C/C++,因为词典、翻译、OCR 这些客户端和服务端模块,很多高并发核心组件就是C++写的,所以卷子里会考察C++对象模型、虚函数表、内存对齐这类知识点,Java 方向的考生也能选做Java题,但C++相关的题目占比明显更高。
另一个特点是,有道面向的是工具类产品,整体风格比较务实,编程题很少出那种数学味很浓、绕来绕去的题目。它更愿意把实际需求抽象成一个模型,考察你把问题拆成代码的能力。比如处理字符串、统计TopK、迷宫最短路径这类,都是业务里常见的场景。所以备考的时候,与其背一堆复杂的算法竞赛模板,不如把常用数据结构和经典思路练到能即写即对。这一点我在后文会有更详细的分析。
对于目标是网易的同学,我的建议是提前搞清楚你想投的是哪个业务线,然后针对性地复习。去查一下这个业务线的产品形态,大概率能猜出它重视的技术方向。有道这种工具类产品线,重点看基础工程能力和算法落地能力;游戏部门会更偏向图形学、性能优化;云音乐这类内容平台则更重视推荐算法和数据处理。校招笔试的备战,本质上是一场信息战加体力战,你对目标业务线的了解越多,复习方向就越明确。
2. 选择题目背后的知识点:吃透这几块基本就稳了
2.1 数据结构与算法:从二叉树到排序复杂度
选择题里考察数据结构的题,一般不会问特别冷门的概念,而是围绕"写代码时真的会用到"的知识点来考。比如二叉树的三种递归遍历方式,看起来很简单,但笔试会换一个方式考你:给定某两种遍历序列,能否唯一确定一棵二叉树?答案是需要中序加上前序或后序之一,因为只有中序能提供左右子树的切分依据。这个考点几乎每年都会换皮出现,本质上考的是对遍历过程的理解,而不只是背代码。
同样是树这一块,平衡二叉树(AVL)和红黑树的区别也经常出现在选项里。很多人在这里吃亏,是因为记混了旋转条件和应用场景。我自己有一个简单的记忆方法:AVL是严格平衡的,任意节点的左右子树高度差不超过1,所以查找效率高,但插入删除时旋转频繁;红黑树是弱平衡的,最长路径不超过最短路径的两倍,插入删除代价更小,所以被广泛用在 STL map 和 Java 的 TreeMap 里。选择题如果考这一类,你需要做的不是死记对比表格,而是理解它们各自解决了什么问题,这样无论题目怎么换角度,你都能答对。
排序算法是笔试选择题的常青树,常见考法有两类:一类直接问某种排序在最坏情况下的时间复杂度或空间复杂度;另一类给出一个序列的中间过程,让你判断这是什么排序。要应对这两类题,我建议你把下面这张表熟记,并且能手推快排和归并排序的过程:
| 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 |
| 快速排序 | O(n log n) | O(n²) | O(log n) | 不稳定 |
| 归并排序 | O(n log n) | O(n log n) | O(n) | 稳定 |
| 堆排序 | O(n log n) | O(n log n) | O(1) | 不稳定 |
这里有一个容易被忽略的点:很多人只记住快排平均复杂度是 O(n log n),却忘了最坏情况是 O(n²),而手写快排如果不做随机化,一旦输入接近有序就会触发最坏情况。笔试试卷里如果考这个,往往踩中的就是这个盲区。另外,希尔排序是O(n log n)到O(n²)区间,具体看增量序列,属于冷门考点,但偶尔会在多选题里出现,备考时留意一下它的不稳定特性和增量选择方式即可。
2.2 计算机网络与操作系统:高频考点的正确打开方式
计算机网络这块,大方向很稳定:TCP/IP 协议族为主,HTTP 为辅。三次握手几乎每年必考,但考得很细。比如"SYN Flood 攻击利用的是 TCP 握手的哪个阶段""服务器收到第三次握手的 ACK 之后,连接进入什么状态",这些都是从三次握手衍生出来的问题。复习时不要只背握手的图示,要理解每个状态转换的触发条件,尤其是 LISTEN、SYN_SENT、SYN_RCVD、ESTABLISHED 这几个状态。如果选择题里出现状态名,它不会绕弯子,考的就是你有没有真正看懂状态机。
滑动窗口和拥塞控制也常常出现,尤其是慢启动、拥塞避免、快速重传、快速恢复四个阶段。我建议你把拥塞窗口的变化过程自己手画一遍:时间轴、阈值位置、超时事件和收到三个重复 ACK 时窗口分别怎么变化。画清楚之后,这类题目基本就是送分。这里有一个备考技巧:把所有TCP状态名写到一张A4纸上,自己对着纸讲一遍"客户端发送SYN后进入什么状态、服务端收到后返回什么",讲得出来就说明真的懂了,讲不出来就回去翻书。这种输出式复习的效果,比默读三遍书都要好。
操作系统的死锁四个必要条件也是必考:互斥、持有并等待、不可剥夺、循环等待。网易有时候会反过来考,给你一个场景,让你判断破坏的是哪个条件。比如"一次性申请所有资源"破坏的是"持有并等待",这种题目只要记住四个条件的含义就不难。我的建议是不要死记条件名称,而是用一个两个进程各自拿着一个资源、然后互相等对方释放的例子推一遍,这个例子足以覆盖所有条件的理解。
内存管理里的分页和分段也是高频考点。分页是物理层面的做法,页大小固定,地址由页号和偏移量组成;分段是逻辑层面的划分,段长可变,便于共享和保护。选择题常问的是系统出现缺页异常时,指令地址怎么转换、页表项的哪些位影响置换策略、LRU与FIFO缺页次数怎么计算。虚拟内存的页面置换算法,至少要能手算LRU、FIFO、Clock三种的缺页次数。这属于计算型题目,动手算过几遍以后就不容易丢分,而且只要算熟了,速度会越来越快。
2.3 数据库与语言基础:索引失效、事务隔离和虚函数
数据库的题量不大,但几乎每次都围绕索引和事务出题。索引必考的是哪些情况下索引会失效,常见的失效场景有:对索引列使用函数或运算、隐式类型转换、like 以通配符开头、or 条件里包含非索引列。这种题在笔试卷里其实算送分题,前提是你真的在 MySQL 里执行过 EXPLAIN 看过执行计划。如果没有条件实机验证,至少要把执行计划 type 的优先级记清楚:system > const > eq_ref > ref > range > index > ALL,看到 type 为 ALL,基本可以断定发生了全表扫描。
事务部分考的是ACID特性和隔离级别。四种隔离级别(读未提交、读已提交、可重复读、串行化)对应解决脏读、不可重复读、幻读的问题,这个对应关系需要记牢。MySQL 默认是可重复读,InnoDB 通过 MVCC 加间隙锁解决了大部分幻读场景,但不代表完全杜绝。这类题目在选择题中会以一个具体的并发场景来问你会产生什么问题,比如"事务A读到事务B未提交的数据,属于哪种异常",踏踏实实把隔离级别和异常对应表背下来就能拿分。
语言基础方面,C++和Java都会考。C++的虚函数、静态成员、内存布局基本上是雷打不动的考点。C++虚函数的核心是 vtable,只要类里出现 virtual 关键字,实例内存里就会先存一个指向 vtable 的指针,这就解释了为什么含有虚函数的类实例会多出4或8字节。关于这个知识点,我推荐你在自己的电脑上把下面这段代码跑一遍,用sizeof打印类的大小,亲眼看到加上virtual后实例从12变成16(32位系统上),比你背十遍结论都管用:
#include <iostream> using namespace std; class A { int x; char y; }; class B { int x; char y; virtual void f() {} }; int main() { cout << sizeof(A) << endl; cout << sizeof(B) << endl; return 0; }Java方向则常考JVM内存区域划分、垃圾回收算法,以及ArrayList与LinkedList的区别。你不需要把JVM各种启动参数背熟,但堆、栈、方法区各自存什么必须分清,新生代、老年代的对象流转过程也要能讲出来。这里多说一句:语言题考的是"你在实际写代码时对这些机制的理解程度",而不是你能不能背出JVM规范原文。用生活化类比来理解会轻松很多——JVM堆就像一个大仓库,对象是货物,垃圾回收器是定期来清理过期货物的管理员,新生代是快速周转区,老年代是长期存放区。
3. 编程题实战:三道典型题从思路到AC
3.1 字符串处理题:用单调栈解决去重与字典序问题
有道这套笔试卷里,有印象的编程题中有一类是字符串处理,题目背景是给定一个字符串,要求删除重复字符,使得剩余字符的相对顺序不变,并且字典序最小。很多人第一反应是遍历字符串、用哈希集合去重,但这样只能保证相对顺序,无法保证字典序最小。正确解法是用单调栈的思路:维护一个栈,遍历字符,如果当前字符比栈顶字符小,且栈顶字符在后面还会出现,就把栈顶弹出,最后把当前字符入栈。
这里的关键在于"后面还会出现"这个条件,所以你需要预先统计每个字符在剩余部分出现的次数。我当时的实现方式是维护一个 last 数组记录每个字符的剩余出现次数,再配合一个 used 数组记录栈中是否已有该字符。核心代码可以写成下面这样:
string removeDuplicateLetters(string s) { vector<int> last(256, 0), used(256, 0); for (char c : s) last[c]++; string res; for (char c : s) { last[c]--; if (used[c]) continue; while (!res.empty() && res.back() > c && last[res.back()] > 0) { used[res.back()] = 0; res.pop_back(); } res.push_back(c); used[c] = 1; } return res; }这道题的时间复杂度是 O(n),空间复杂度是 O(n),已经是理论最优。如果你能在笔试场景里写出这版代码,并且把边界情况(比如全重复字符、空字符串)都想到,基本能拿到满分。实测下来,这道题最容易错的地方不是整体思路,而是 used 数组的置位时机——只有真正入栈的字符才需要标记为已使用,一旦被弹出就要立刻清除标记。很多同学在压力状态下会漏掉清除标记这一步,导致同一个字符被错误地跳过入栈,结果就错了。我的建议是,写完代码之后手动用三个用例跑一遍:原始字符串是 "cbacdcbc",期望结果是 "acdb";全是重复字符 "aaaa",结果应该是 "a";空字符串直接返回空。
3.2 动态规划题:最长不下降子序列的坑和优化
另一道比较有代表性的编程题是最长不下降子序列的变体。经典 LIS 有两种解法:O(n²) 的DP和 O(n log n) 的贪心二分。笔试限时环境里,我推荐直接写 O(n log n) 版本,因为代码短,不容易被极端数据卡超时。核心思想是维护一个数组 d,d[i] 表示长度为 i+1 的递增子序列的末尾元素的最小值。遍历每个数,在 d 里二分查找第一个大于等于它的位置进行替换;如果找不到合适位置,就追加到末尾。最终 d 的长度就是最长递增子序列的长度。
我实际踩过的坑是"不下降"和"严格递增"的区别。不下降意味着相等元素可以共存,二分查找时要用 upper_bound;严格递增则要用 lower_bound。两个函数差一个,答案就可能差得很远。比如输入 [1,1,1],如果题目要求不下降序列,正确答案是 3,你用 lower_bound 就会得到 1。很多人在读题时忽略了"不下降"这几个字,直接套模板,结果全军覆没。这里我强烈建议:拿到题目第一件事,把"递增""不下降""递减""严格"这些限定词圈出来,再用一组相等元素验证一下自己的理解,可以避免大量失分。
如果题目附带要求输出具体的最长不下降子序列,而不是只返回长度,那就需要多维护一个数组记录每个位置是由哪个位置转移来的。最后从后往前回溯得到完整序列。这个扩展在笔试里偶尔出现,建议平时就在代码模板里准备好。另外,动态规划的编程题在试卷里通常不止一道,如果时间允许,优先选择状态定义和转移方程更清晰的那道来写,因为DP写起来容易,debug也要花时间,要挑性价比最高的题先做。
3.3 最短路径类题:网格DP与Dijkstra的取舍
编程题里图相关的题目,我个人印象比较深的一道是:给定一个 m×n 的矩阵,每个格子里有一个数字,要求找从左上角到右下角经过位置的数之和最小,移动方向只能向右或向下。这其实不是真正的图论题,而是二维DP,转移方程是 dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]。边界初始化时需要特殊处理:第一行只能从左往右累加,第一列只能从上往下累加,dp[0][0] 直接等于 grid[0][0]。很多人在做题时忘记处理这些边界,导致数组越界,笔试环境里编译器未必报错,但运行结果就是错的。
如果遇到真正的带权图最短路问题,建议优先使用 Dijkstra 而不是 SPFA。原因很简单:考试环境的数据不保证不存在极端构造,Dijkstra 只要边权非负就是稳定 O(E log V),用优先队列实现代码量也可控。SPFA 在随机数据上表现不错,但在有向带环图上可能存在被卡到退化的风险。平时训练时我依然建议大家把两种算法都写熟,但考场上求稳更重要,能用 Dijkstra 的题就不要为了一时的代码简洁去赌 SPFA。
还有一个容易被忽略的边界:矩阵的长宽可能为1。当 m 或 n 等于1时,不适用二维DP的转移逻辑,整条路径只有一条。我的做法是在 DP 之前先做特判,如果 m 和 n 有一个为1,直接把路径上所有数字求和返回。这种边界处理思路,本质上是在考察你写工程代码时对异常输入的敏感程度,而很多算法训练恰恰很少覆盖这种情况。
3.4 笔试现场的做题顺序与代码调试技巧
编程题做不出来或者做错,很多时候不是能力问题,而是策略问题。我的建议是,拿到编程题先花一两分钟通读全部题目,挑一道最有把握的先做,不要按题号顺序死磕。先把会做的拿到分,再回头啃难题。每道题动笔之前,先在草稿纸上写一个小例子,用手跑一遍完整流程,确认思路无误再写代码。这个习惯的作用,是帮你过滤掉一半以上的"思路想当然"类错误。
写代码时,边界条件是最容易出错的地方。空数组、只有一个元素、全部元素相同、数组长度为1、输入已经是排好序的,这些用例每道题都要测一遍。我甚至建议准备几张草稿纸,每张纸上把常见边界条件列在角落,写代码之前先看一眼,测完之后再划掉。在线笔试环境里通常没有IDE的断点调试功能,你能依靠的只有 printf 或 cout 打日志,关键分支一定要打,尤其是二分、递归、动态规划的转移处。日志不要打太多,重点是在出错时能精确定位是哪一步逻辑不对。
关于复杂度分析,我想强调一点:编程题虽然阅卷系统主要跑用例,但如果满分是100分,有些题的提交面板上会要求填写时间复杂度和空间复杂度,这也会被记录下来供后续面试官复盘。即使系统不强制填写,你在代码注释里写清楚核心算法的复杂度,也会给阅卷人留下更好的印象。毕竟笔试之后往往还有面试官回看代码的环节,代码的规范程度直接决定了面试官愿不愿意给你进入下一轮的机会。
4. 备考策略与避坑指南:把有限时间用在刀刃上
4.1 两小时答题时间怎么分配
这套卷子时长两小时,看起来够用,一旦进入思考状态,时间就过得非常快。我推荐的节奏是:拿到试卷先花5分钟把所有题目扫一遍,对题目分布和编程题难度建立基本判断;然后花35分钟做选择题和填空题,遇到不会的先标记跳过,不要在一道题上耗太久;剩下的80分钟全部给编程题,每题先写思路再写代码;最后留出5分钟检查选择题答案和编程题的边界条件。
关于选择题是否要改答案,我的观点很明确:不要轻易改,除非你突然有了明确的计算依据或者发现自己看错了题目,否则第一直觉在大多数情况下是对的。我当年参加笔试时,有好几次想把第一轮选的答案改掉,最后验证下来第一直觉反而更可靠。这个现象的背后逻辑是,你在第一轮读题时接收的信息最完整,大脑经过潜意识计算给出的答案往往比紧张状态下反复纠结的结论更接近正确。
编程题的顺序安排上,如果三道题难易分明,先做最容易的;如果难易不明,先做你最有把握的题型。做题过程中一旦卡住超过20分钟,果断换下一题,不要在死胡同里钻牛角尖。编程题通常是按测试用例给分,你能拿下一部分用例就算赚到,最后一题如果实在没思路,可以写一个暴力解,能过部分测试点也是分。暴力解有时候并不丢人,它至少证明你能把问题建模成可执行代码,比空着交卷强得多。
4.2 三到四周冲刺复习路线图
如果距离笔试还有三到四周,我建议的路线是:第一周主攻数据结构和算法,把线性表、树、图、排序、查找这些基础过一遍,配合LeetCode高频题刷50到60道。这里有个小技巧,不要按题号顺序刷,而是按知识点分类刷,每个知识点集中练10到15道,形成肌肉记忆。第二周把计算机网络和操作系统的基础题刷完,重点放在TCP状态机、内存管理和进程调度,可以在题库里筛选对应标签的题目集中练习。第三周集中写编程题,保证每天至少3道,并且每一道都完整模拟笔试环境,用记事本写、不依赖IDE自动补全,调试只用打印语句。第四周做整套模拟题,卡时间做,模拟真实考试的紧张感。
语言基础不需要单独抽出一整周时间,它更像是平时写代码顺手积累的知识。考前把虚函数、内存布局、集合类源码这几个常考模块快速过一遍就好。C++方向的同学重点看虚函数表和 const 的各种用法,Java方向的同学重点看 HashMap、ArrayList 源码和JVM内存区域。这些知识点在笔试选择题里出现的频率很高,但复习成本其实不高,性价比很划算。
4.3 笔试里的隐形扣分点
笔试除了代码正确性,还有几个隐形扣分点很容易被忽视。第一个是代码风格。虽然线上阅卷系统不会直接看你的缩进和命名,但如果笔试之后还有面试官复盘卷子的环节,代码的可读性就会影响印象分。命名规范、注释适度、函数体不要超过几十行,这些习惯要提前养成。第二个是复杂度分析的表述。编程题有时候会要求你说明时间复杂度和空间复杂度,不只要写代码,还要在注释或提交说明里写清楚。第三个更现实的问题:在线笔试的编译器版本可能和本地环境不同,个别语法(比如C++的结构化绑定、某些高版本的STL特性)在考试环境不一定被支持,所以平时写题尽量用保守语法,别在考场上赌编译器支持新特性。
还有一个很多人忽略的坑:多选选择题的评分规则。有些平台多选少选不得分,有些平台少选给一半分,这个信息在考试说明里会写,但很多人一紧张就忘了。如果规则是"少选得一半分、多选错选不得分",那么遇到不确定的选项,宁可选少也不要选多。我见过不少同学在多选题上因为多选了一个不确定项而整题丢分,非常可惜。做题前花30秒看一遍答题说明,这个习惯能帮你避免很多无谓失分。
4.4 从这套卷子反推大厂研发工程师的能力模型
把这张卷子放到整个校招流程里看,它考的不只是一个一个孤立的知识点,而是一个研发工程师的基础能力模型。选择题覆盖的是技术广度,编程题考的是工程实现能力。网易有道作为工具型产品线,希望招进来的人既能快速上手业务代码,又能在高并发或算法场景下做出有质量的设计。所以备考笔试的时候,不要只盯着"过笔试"这个目标,而是借这个机会把计算机基础补扎实。
这套基础能力不是考完就扔的。后续每一轮技术面试都会不断复用这些知识,入职后的日常工作也会遇到它们的影子。我在带新人的时候有一个很深的体会:基础扎实的人,上手业务代码的速度通常更快,因为他们看到问题能迅速理解底层机制,而不是停留在"这样调一下能跑就行"的层面。所以如果你在准备笔试的过程中发现自己某些基础模块比较薄弱,这反而是好事,提前暴露问题总比入职后再补要好得多。
5. 写在后面的话
2018年那套卷子到现在,题面内容已经更新了很多轮,但考察的骨架几乎没有变。我后来在校招季里参与出题和看卷,发现真正能拿高分的同学,往往不是知识面最广的,而是那些把基础题练到极致、编程题边界条件想得最全的人。如果你正在准备研发工程师的校招笔试,我建议把这张卷子的考点当成一面镜子,对着镜子一项一项查漏补缺,比漫无目的地刷几百道题要高效得多。
还有一个经验想分享给各位:每次做完一套模拟题,把错题涉及的知识点记录在一个本子上,一周后回头再重做一遍错题。这个习惯我在备考时坚持了整整一个月,效果远比反复开新题要好。笔试的本质不是比谁见过的题多,而是比谁在有限时间内暴露的问题少,错题复盘就是压缩暴露问题成本最有效的方式。把基础打牢,把节奏练出来,笔试也就没那么可怕了。希望这篇复盘能帮到你,祝准备校招的各位都能收到心仪方向的面试邀请。