ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

网易算法岗笔试复盘:KMP、贪心、二分图与状态压缩DP全解析

网易算法岗笔试复盘:KMP、贪心、二分图与状态压缩DP全解析 网易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²)的DPn≤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个字符没有前后缀概念按这个定义为0next[1]子串a最长相等前后缀长度是0next[2]子串ab最长相等前后缀长度是0next[3]子串aba前缀a和后缀a相等长度1next[4]子串abac最长相等前后缀长度0next[5]子串abaca前缀a和后缀a相等长度1next[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是8n是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(VE)如果用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刷题时要注意递归深度限制有些平台默认递归深度只有1000DFS超过这个深度会直接报错。如果题目数据范围大又必须用DFS要么改成迭代栈要么用sys.setrecursionlimit调大上限。4.3 面试衔接从笔试到面试的加分项笔试过了只是第一步面算法工程师岗位时面试官大概率会问你笔试中某道编程题的思路。所以考后一定要复盘把每道算法题的最优解写在代码文件夹里最好写清楚的注释。我考完后还会分类整理成博客一方面加深记忆另一方面春招时可以直接翻出来复习。特别是在写KMP的时候如果面试官问next数组为什么能优化匹配效率你要能说清楚“最长相等前后缀是失配时模式串回退的依据”这样回答才能体现你真正理解了KMP而不是背模板。面试官通常很反感“我背了这个算法所以会写”的答案他们想听到的是“我理解这个算法所以能灵活变通”。另外网易有道偏向AI应用落地如果简历里有机器学习项目面试时大概率会深挖你的模型评估方法、数据清洗流程、特征工程思路。笔试中的机器学习选择题虽然占比不大但面试衔接时往往就是你被追问的起点。比如笔试考了KNN三要素面试就可能问你“如果K值取太大或太小会怎么样”。准备好这类延伸问题能让你的面试表现明显区别于其他候选人。考完这套题我最大的体会是算法笔试题真正拉开差距的地方往往不是最难题而是你“以为你会但其实不会”的基础题。KMP的next数组、排序稳定性、KL散度不对称这种知识点真正搞懂一遍比刷十道重复的模板题有用得多。另一个体会是跨领域的知识面比想象中重要——模拟退火、PID、Sobel算子这些看起来跟“算法工程师”不搭边的内容也会出现在卷子上。如果你也在准备下一批校招希望这份复盘能帮你把复习范围收窄到真正重要的地方。祝顺利上岸。
返回列表