网易2020校招笔试的算法工程师(有道)提前批,这套题我考完当天晚上就想写点什么,但一直拖到第二天才动笔。原因很简单,考场上被几道选择题恶心到了,考完查资料才发现是自己在复习时漏掉的知识点,越想越亏。先说结论:这套卷子整体难度不算高,没有那种让人无从下手的压轴怪题,但覆盖面极广,从KMP的next数组一直考到PID控制,从排序稳定性考到KL散度,编程题反而是最常规的四种类型:字符串匹配、贪心、二分图匹配、状态压缩DP。
如果你准备投算法岗,尤其是大厂的提前批,这份复盘应该能帮你少走不少弯路。我会从题型结构、四道编程题的完整思路、选择题里隐藏的知识点体系,以及我踩过的坑这几个方面展开,比较适合正在准备校招笔试的同学,也适合想查漏补缺的工程师。先说清楚,这不是官方解析,是我考后根据回忆整理的笔记,题目描述可能有偏差,但考点和思路是实打实的。
1. 开考前的准备:题型分布与实际考试节奏
1.1 我参加的那场笔试:时间、形式与题型构成
网易有道提前批的笔试,以我参加的这次为例,全程在牛客网线上完成,总时长120分钟。题量大概是单选题约10道,多选题5道,编程题4道。笔试平台支持本地IDE调试后粘贴代码,也支持在线编辑,提交后会立刻看到部分用例通过情况,平台会提示通过率,但不会告诉你具体哪个用例挂了,这一点比较磨人。
选择题的分值占比其实不低。很多人把精力全放在编程题上,结果选择题错得很惨。单选的考点非常杂,从数据结构到机器学习都有;多选更坑,少选、错选都不得分,所以拿不准的宁可少选。我当时的多选策略就是:只选百分百确定的,犹豫的选项一律不勾,宁少勿错。
另外要提醒一点,线上笔试的编辑器虽然支持大部分常用快捷键,但和本地IDE的手感还是有差距。我平时习惯用IDE的自动补全,考场上切换到网页编辑器后,写代码速度明显下降,尤其是大括号和缩进需要手动处理的地方。建议考前至少用牛客或赛码的模拟环境练三次,把这种不适感提前消掉。
1.2 做题节奏:先选择题还是先编程题
我的习惯是拿到卷子先花两分钟把四道编程题全部扫一遍,不用细读,只要判断出每道题的题型和大致难度,心里有个预期。然后从第一道编程题开始做,做完两道之后再回头快速过选择题,最后再做剩下两道编程题。
为什么这么安排?因为选择题是“会就会、不会就蒙”,你花再多时间也未必能做对,但编程题只要思路对了,时间就花在实现上。提前把简单题做掉,留下充足时间给后面的DFS、DP,是最稳的策略。时间分配上,四道编程题我大致按照4:5:6:7的比例排时间,后面两道难题留的时间更多。
这里有个很实用的小技巧:扫编程题的时候,在草稿纸上把每道题的数据范围记下来。数据范围能直接告诉你该用什么复杂度的算法,比如n≤10大概率是状态压缩或全排列,n≤1000可能是O(n²)的DP,n≤10^5大概率是O(n log n)甚至O(n)。这道题考场上我就是靠这个快速锁定了状态压缩DP的方向。
2. 代码题全复盘:四道题目的解题思路与陷阱
2.1 字符串匹配题:不只考KMP,还考next数组理解
第一道编程题是典型的字符串匹配,要求判断给定文本串中是否包含某个模式串,并输出首次匹配的位置。题目本身不难,但如果直接用暴力匹配,后面几组大数据的测试用例会超时,所以核心考点就是KMP算法。
这道题最阴的地方在于:它没有直接让你写KMP,而是考你对next数组的理解。题目给了一个模式串 "abacaba",问它的next数组(按next[i]表示前i个字符组成的子串的最长相等前后缀长度来定义)是多少。如果你只背过KMP模板,不太清楚next数组是怎么算出来的,很容易在边界上翻车。
我推导一遍:
- next[0] = 0(第0个字符没有前后缀概念,按这个定义为0)
- next[1],子串"a",最长相等前后缀长度是0
- next[2],子串"ab",最长相等前后缀长度是0
- next[3],子串"aba",前缀"a"和后缀"a"相等,长度1
- next[4],子串"abac",最长相等前后缀长度0
- next[5],子串"abaca",前缀"a"和后缀"a"相等,长度1
- next[6],子串"abacab",前缀"ab"和后缀"ab"相等,长度2
所以next = [0, 0, 0, 1, 0, 1, 2]。很多同学写的是这个答案,但有的教材把next[0]定义为-1,答案就完全变了。做题前先确认题目对next的定义是哪种,这个小细节就是送命题和送分题的区别。
实际的编程题部分,我用的是常规KMP实现,失配时通过next数组回退,而不是回退到模式串开头,否则时间复杂度会退化到O(n*m)。代码如下:
def build_next(p): m = len(p) nxt = [0] * m j = 0 for i in range(1, m): while j > 0 and p[i] != p[j]: j = nxt[j - 1] if p[i] == p[j]: j += 1 nxt[i] = j return nxt def kmp_search(s, p): n, m = len(s), len(p) if m == 0: return 0 nxt = build_next(p) j = 0 for i in range(n): while j > 0 and s[i] != p[j]: j = nxt[j - 1] if s[i] == p[j]: j += 1 if j == m: return i - m + 1 return -1边界条件记得处理:文本串为空时返回-1,模式串为空时返回0,模式串长度大于文本串时直接返回-1。这些隐藏用例很容易让全AC变成部分AC。
2.2 区间调度题:贪心策略与排序依据
网易一直很爱考贪心,有道这场的第二题是区间调度类问题:给出一组任务的开始时间和结束时间,每个任务需要占用一个资源,问最少需要多少个资源才能不冲突地完成所有任务。
这道题的经典解法是贪心加排序。先把所有区间按开始时间排序,然后维护一个小顶堆,堆里存的是当前已分配资源的结束时间。每来一个新任务,先看堆顶元素对应的结束时间是否小于等于当前任务的开始时间,如果是,说明这个资源已经空出来了,可以直接复用(弹出堆顶再压入新任务的结束时间);否则需要新开一个资源。
这样做的正确性在于:每次我们都优先复用最早结束的资源,这是最“不浪费”的选择。贪心选择性质可以通过交换论证证明,即总是存在一个最优解包含这个贪心选择。虽然考场上不用写证明,但理解这一点能帮你放心用这个策略。
这里有个细节:不能只按开始时间排序,也不能只按结束时间排序。我之前见过有人按区间长度排序然后贪心,局部看起来合理,但整体会出错。区间调度问题有两个经典变体——最大不重叠子集是优先按结束时间排序,最少资源占用则是按开始时间排序加堆处理。考场上一定要分清是哪个变体,两者排序依据完全不同,搞混了就是整道题暴毙。
复杂度上,排序O(n log n),堆操作O(log n),总体O(n log n),完全能跑过。我提交后20个测试用例全部通过,算是比较顺利的一题。
2.3 任务分配题:二分图匹配的建模
第三题考的是二分图匹配。场景大概是:有若干个岗位和若干名候选人,每个候选人只能匹配到部分岗位(题目给了一个二维的匹配关系表),问最多能同时分配多少组。这是个裸的最大二分匹配问题。
数据范围不大,n和m都在100左右,所以匈牙利算法能过。我当时犹豫了一下要不要写HK算法,后来发现没必要——匈牙利最坏情况O(VE),对这道题的数据量完全够用。如果你刷题时只背了Dinic或HK,没写过匈牙利,建议还是把匈牙利练熟,笔试里出现频率非常高。
匈牙利算法的核心是增广路:对左侧每个节点做一次DFS,尝试找一个未匹配的右侧节点,或者通过递归让已经匹配的右侧节点“让位”出去,让出位置给当前节点。这个“让位”的过程要用一个visited数组避免死循环,每轮DFS之前都要清空。
参考实现:
def dfs(u, match, visited, graph): for v in graph[u]: if not visited[v]: visited[v] = True if match[v] == -1 or dfs(match[v], match, visited, graph): match[v] = u return True return False def max_match(n, graph): match = [-1] * len(graph) res = 0 for u in range(n): visited = [False] * len(graph) if dfs(u, match, visited, graph): res += 1 return res这道题真正容易错的地方不是算法本身,而是图的构建。题目给的匹配关系表可能是候选人到岗位的布尔矩阵,也可能反过来,读题不仔细就会把左右集合搞反。建模时我习惯用一个二维数组存边,左侧节点从0开始编号,右侧节点单独编号,避免冲突。
2.4 状态压缩DP:看似暴力实则优化的典型
最后一题是状态压缩DP,在网格上做文章:给一个m×n的网格,里面有一些格子不能走,要求从左上角走到右下角的方案数,但移动方向不是单纯的向右向下,而是带有一定的跳跃规则,所以需要压缩状态来记录当前行的影响。
这类题的套路是先看状态范围,如果m和n有一个很小(比如小于10),大概率就是状态压缩DP。用二进制表示某一行的格子占用情况,或者表示当前可达状态集合。我那道题的m是8,n是100,明显要把m压进二进制状态里。
这道题我没有AC完整,只过了前几个测试点。原因是我在状态转移时没有处理好“当前行被上一行的落点影响”这个细节。复盘时我把所有状态枚举一遍,发现状态合法性的判断条件写错了,把“下一个跳跃必须在网格内”写成了“只要不超过边界就算合法”,漏掉了障碍格。这是一个非常隐蔽的bug,不打印中间状态根本看不出来。
状态压缩DP的起步成本比较高,考场上如果时间紧张,可以先拿暴力DFS拿部分分,再逐步加记忆化。网易笔试和很多大厂一样是部分得分制,能过多少用例就有多少分,别因为没做出正解就全丢。
3. 选择题里的隐藏考点:从一道题引申出的算法知识体系
3.1 排序与基础数据结构:稳定性的送命题
选择题里几乎必考排序。网易的考法通常很直接:给你四个排序算法,问哪个是稳定的。归并排序和冒泡排序稳定,快速排序、堆排序、希尔排序不稳定,插入排序稳定。这些是基础中的基础,但就是有人会在“堆排序是否稳定”这种题上翻车。
还有排序复杂度的比较。我整理过一张表,考前值得反复背几遍:
| 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 稳定性 | 额外空间 |
|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | 稳定 | O(1) |
| 插入排序 | O(n²) | O(n²) | 稳定 | O(1) |
| 归并排序 | O(n log n) | O(n log n) | 稳定 | O(n) |
| 快速排序 | O(n log n) | O(n²) | 不稳定 | O(log n) |
| 堆排序 | O(n log n) | O(n log n) | 不稳定 | O(1) |
| 希尔排序 | O(n log n)~O(n²) | O(n²) | 不稳定 | O(1) |
网易笔试不会考你推导过程,但会考结论。比如“快速排序在什么情况下最坏”“堆排序建堆的时间复杂度”“归并排序为什么稳定”这类问题。理论上,归并排序稳定是因为合并时相同元素的相对顺序不会改变;快速排序不稳定是因为交换操作会打乱相同元素的相对顺序。这些原因最好也理解,面试时被追问的概率不小。
3.2 机器学习与深度学习:概念题的常见坑
有道这个部门偏AI应用,所以机器学习、深度学习的选择题占了好几分。我印象比较深的有几道:KNN算法、KL散度、XGBoost。
KNN那题问的是“KNN算法的应用能力包括哪三个方面”,答案就是三要素:距离度量方式、K值的选择、分类决策规则(通常是多数投票)。这也提醒我们复习时要回到教科书层面的细节表述,很多概念题就是在考定义。如果你只会在sklearn里调用KNN,这种题很难答对。
KL散度那道题考的是性质:KL散度非负,但不具有对称性,KL(p||q)不等于KL(q||p),所以它不是严格意义上的距离度量。这个考点如果只做过深度学习框架的API,没有从信息论角度理解过,很容易选错。我当时就把“非负”和“对称”理解成等价的了,实际上非负是对的,对称是错的。
XGBoost考的是基础原理:它是梯度提升决策树(GBDT)的改进,通过二阶泰勒展开利用损失函数的一阶导和二阶导,并加入正则项防止过拟合。选择题问“XGBoost相比GBDT的主要改进是什么”,选项里有“使用二阶导数信息”和“加入正则项”,这两个都是对的。所以我个人经验是:多选里看到两个都正确的选项,别因为“看起来像送分”就只选一个。
3.3 经典优化算法与控制算法:容易被忽略的边界
这部分是相对冷门的考点,但网易确实考了。我做题时遇到了模拟退火、粒子群、PID相关的概念题。
模拟退火考的是判断算法描述正误:如果新解比当前解更差,它以一定概率接受,这个概率随温度降低而减小。选项里有一个说“一定会接受更差的解”,这是错的。模拟退火的核心思想是在高温阶段允许接受较差解来跳出局部最优,低温阶段逐渐收敛。
粒子群算法考的是速度更新公式里“个体历史最优(pbest)”和“全局历史最优(gbest)”两个引导项,问哪个参数控制对全局的探索能力。这类题的关键是考前知道粒子群算法在迭代时,每个粒子会同时参考自己的历史最优位置和整个群体的历史最优位置来调整速度,而不是纯随机搜索。
PID则出现在一道控制类的题里:增量式PID和位置式PID的区别。增量式PID输出的是控制量的增量,只需要最近三次的误差值,不需要累积误差。如果你没接触过控制理论,这道题就只能靠排除法。我建议复习时不要只盯着机器学习,模拟退火、遗传算法、粒子群、贪心这些“经典算法”的选择题出现频率比想象中高,它们不涉及复杂推导,考的就是理解。
3.4 图像、数值与图论:跨领域考点整理
图像算法也考了一道,选项里出现了Sobel算子、拉普拉斯算子、均值滤波、中值滤波,问哪个算子用于边缘检测。Sobel算子和拉普拉斯算子都用于边缘检测,前者是一阶微分算子,基于梯度幅值;后者是二阶微分算子,对噪声比较敏感。均值滤波和中值滤波则是平滑去噪的,中值滤波对椒盐噪声特别有效。这些知识点在图像处理课程里是基础,但对纯算法岗的同学来说可能很久没碰了,建议考前扫一遍常用算子的用途。
数值算法方面,卡尔曼滤波、快速幂、Dijkstra,虽然我这场没直接考,但同类笔试题库里出现概率很高。卡尔曼滤波的核心就两个步骤:预测和更新,选择题一般问“预测阶段做什么”,答案是“利用状态转移矩阵和上一时刻的最优估计来预测当前时刻状态”。快速幂则是考递归或迭代实现,O(log n)的复杂度,常用于大数取模。Dijkstra是单源最短路,不能处理负权边,选择题经常把这个当成陷阱。
图论里还有一个容易被忽略的点是拓扑排序。考法通常是给你一个有向图的边集,问是否存在环。拓扑排序的复杂度是O(V+E),如果用DFS实现,还要区分“访问中”和“已访问”两种状态,否则判断环会出错。
3.5 字符串与检索算法:网易出题的“舒适区”
热搜词里出现了BM25、DC3、音频重采样、井字棋Minimax这些偏门词,虽然不全是网易的常考方向,但反映了算法岗笔试的命题潮流:越来越喜欢考“有实际应用背景”的算法题,而不是纯理论。
BM25是信息检索领域经典的排序函数,在ES等搜索引擎里被广泛使用,考选择题通常是问它和TF-IDF的区别。DC3是后缀数组的线性时间构造算法,笔试里直接考实现的可能性不大,但可能会考它的复杂度是O(n)。音频重采样属于信号处理领域,和算法岗的交叉点在于插值算法的理解,比如线性插值和sinc插值。这些内容不用深究,但至少要知道名词对应的领域和基本原理。
井字棋Minimax算法则是小型博弈题的经典代表。笔试里如果出现博弈类题,数据范围通常很小,就是用Minimax加Alpha-Beta剪枝。我建议把井字棋的Minimax实现练一遍,十几行代码的事,但能让你彻底理解“极大极小值搜索”这个概念。
4. 复盘之后:给下一批考生的备考建议
4.1 刷题方向:以真题为导向的复习路径
如果只准备两周,我的优先级是:先把排序算法和相关结论背熟,再练数组、字符串、链表相关的数据结构题,然后每天保证刷2道动态规划或贪心题,最后把图相关的BFS/DFS和二分图匹配练熟。网易笔试的整体难度并不算大,主要看熟练度。
有一个特别有用的复习方法:把所有你见过的题目按“考点”而不是“题目来源”分类整理。比如KMP、字符串哈希、BM25都归到“字符串算法”下;Dijkstra、Floyd、二分图匹配都归到“图算法”下。这样考试时你能快速识别题目属于哪一类,直接调用对应算法模板,而不是现场想。
做题顺序上,我建议先刷真题,再刷专题。真题的价值在于让你知道对方真正喜欢考什么。网易历年算法笔试里,字符串匹配、贪心、动态规划是绝对主力,树和图的题也经常出现。如果你投的是AI方向,机器学习基础概念题会多几道,但编程题和投普通后端的人用的往往是同一套题库,这点要注意。
4.2 笔试中的实战技巧:从草稿纸到提交代码
我在笔试里有个习惯:做编程题之前,一定先在草稿纸上写清楚三件事——输入数据的范围、时间复杂度的上限、算法的关键状态定义。输入范围决定你能不能写O(n²)暴力,状态定义决定你的DP能不能转移清楚。这道题如果连状态都不定义清楚就动手,大概率写到一半推翻重来。
如果一道题想了十五分钟还没有任何思路,果断放弃,先做下一道。笔试是得分制,不是竞赛制,与其死磕难题,不如保证前面简单题和中等题正确率。我见过有人在一道状态压缩DP上耗了一个小时,最后前面两道简单题都没时间写完,得不偿失。
还有一个小技巧:提交前留两分钟检查代码的边界条件,比如数组越界、空输入、整数溢出。网易的测试用例一般比较全面,边界数据很容易出现在隐藏用例里。特别是用Python刷题时,要注意递归深度限制,有些平台默认递归深度只有1000,DFS超过这个深度会直接报错。如果题目数据范围大又必须用DFS,要么改成迭代栈,要么用sys.setrecursionlimit调大上限。
4.3 面试衔接:从笔试到面试的加分项
笔试过了只是第一步,面算法工程师岗位时,面试官大概率会问你笔试中某道编程题的思路。所以考后一定要复盘,把每道算法题的最优解写在代码文件夹里,最好写清楚的注释。我考完后还会分类整理成博客,一方面加深记忆,另一方面春招时可以直接翻出来复习。
特别是在写KMP的时候,如果面试官问next数组为什么能优化匹配效率,你要能说清楚“最长相等前后缀是失配时模式串回退的依据”,这样回答才能体现你真正理解了KMP,而不是背模板。面试官通常很反感“我背了这个算法所以会写”的答案,他们想听到的是“我理解这个算法所以能灵活变通”。
另外,网易有道偏向AI应用落地,如果简历里有机器学习项目,面试时大概率会深挖你的模型评估方法、数据清洗流程、特征工程思路。笔试中的机器学习选择题虽然占比不大,但面试衔接时,往往就是你被追问的起点。比如笔试考了KNN三要素,面试就可能问你“如果K值取太大或太小会怎么样”。准备好这类延伸问题,能让你的面试表现明显区别于其他候选人。
考完这套题,我最大的体会是:算法笔试题真正拉开差距的地方,往往不是最难题,而是你“以为你会但其实不会”的基础题。KMP的next数组、排序稳定性、KL散度不对称这种知识点,真正搞懂一遍,比刷十道重复的模板题有用得多。另一个体会是,跨领域的知识面比想象中重要——模拟退火、PID、Sobel算子,这些看起来跟“算法工程师”不搭边的内容,也会出现在卷子上。如果你也在准备下一批校招,希望这份复盘能帮你把复习范围收窄到真正重要的地方。祝顺利上岸。