☰
2020校招算法岗笔试复盘:KMP、贪心与动态规划考点全解析
2026/10/11 1:52:00 网站建设 项目流程

2019年秋天我投了猿辅导的算法岗,参加的是2020届校招笔试的第三套卷子,也就是很多人说的“算法岗三”。这套题给我的整体感受是:不偏不怪,但很考验基础功底的扎实程度。选择题里数据结构和机器学习概念都有涉及,编程题则集中在经典算法模型的变体上,比如字符串匹配、区间贪心、动态规划。今天把这场笔试完整复盘一遍,包括题型设置、解题思路、常见陷阱和备考建议,相信对准备校招算法岗的同学会有参考价值。

我尽量还原当时的题目场景,不保证每个字都和原卷一致,但核心考法和需要具备的能力是一样的。你可以把这篇内容当成一套“带解析的模拟卷”来刷,重点不是背答案,而是理解每道题背后的出题逻辑和做题节奏。

1. 2020校招算法岗笔试题型复盘与备考思路

1.1 笔试整体结构与体验

猿辅导2020届校招笔试用的是在线笔试系统,算法岗三这套卷子整体时长我记得是90分钟,题量不算轻松。题型分两个部分:前面是选择题,后面是编程题。

选择题大概有十几道,覆盖的面比较杂,包括数据结构、算法设计策略、机器学习基础、深度学习基础,甚至还有一两道类似“粒子群算法原理”“PID控制中的参数作用”这种听起来偏工程实际的题目。这部分其实很容易拉开分差,因为很多人只刷剑指Offer和LeetCode,对机器学习概念题准备不足。

编程题一共三道,难度梯度比较明显,第一道偏基础,第二道中等,第三道需要一定的动态规划思维。在线编辑器的自动补全功能很弱,也没有本地IDE顺手,所以平时如果习惯在IDE里刷题,到了笔试环境会有一段时间不适应。这里先提醒一句:最好提前在牛客网、力扣的在线编辑器上练手感,尤其是代码补全和调试方式。

1.2 算法岗笔试到底在考什么

从企业角度拆解,猿辅导是教育科技公司,主要业务涉及在线直播课、辅导产品、智能练习系统,算法团队日常会接触搜索、推荐、用户行为分析、课程内容生成、自适应学习路径规划等场景。但笔试不会直接考业务,而是把这些业务背后的通用能力抽象成算法题。

所以说,算法岗笔试本质考的是三件事:第一,能否用代码准确实现经典算法;第二,能否分析时间复杂度和空间复杂度;第三,能否在有限时间内识别题目的算法模型,并处理边界条件。第三件事最高频的拦路虎就是KMP、贪心、动态规划这类有固定套路但又容易写错的题。

我见过很多同学抱怨“题目刷了两百道还是过不了笔试”,原因多半是只刷数量不总结模型。比如看到“求最大”“求最少”“求方案数”,首先应该想的是贪心还是动态规划,而不是直接上来暴力循环。校招笔试的判题系统只给少量示例,不会提示你超时,也不会告诉你错在哪个用例,所以要想通过,必须在写代码前把算法模型想清楚。

2. KMP算法:高频必考点与next数组破题

2.1 KMP为什么反复出现在校招笔试里

在整理热搜词时,我看到有一条是“在 kmp 算法中,对于模式串 p='abacaba',其 next 数组(next[i] 定义为...”,这几乎就是把当年“算法岗三”的一道选择题原封不动搬出来了。可见这套题对KMP的考察非常直接。

KMP是字符串匹配的经典算法,网上资料很多,但不少人只是背模板,没有真正理解next数组的含义,所以一旦题目换一种定义方式,就容易算错。KMP能高频出现在笔试里,是因为它同时考察了“对暴力匹配缺陷的理解”和“用已经匹配的信息避免重复比较”的优化思想,这在校招面试官眼里是一项很基本的算法素养。

2.2 题目原样与next数组的两种定义

题目一般会给出一个模式串,比如p = "abacaba",然后让求next数组。但这里最大的坑在于,不同的教材、不同题库对next数组的定义不一样。常见有两种:

  • 定义一:next[i] 表示p[0..i]这个子串中,最长相等真前后缀的长度。这个其实就是字符串算法里常说的前缀函数(prefix function)。
  • 定义二:next[i] 表示当p[i]失配时,模式串应该跳回到哪个位置继续匹配。这种写法通常把 next[0] 设为 -1,然后往前错一位。

先说定义一,也就是计算每个前缀子串的最长相等真前后缀长度。对p = "abacaba"可以手算:

  • i=0,子串"a",真前后缀为空,所以 next[0] = 0。
  • i=1,子串"ab",前缀"a",后缀"b",不相等,next[1] = 0。
  • i=2,子串"aba",前缀"a",后缀"a",相等且长度为1;长度2时前缀"ab",后缀"ba",不相等,所以 next[2] = 1。
  • i=3,子串"abac",前缀、后缀逐个看,长度为1时"a"和"c"不等,更长的也不用看,next[3] = 0。
  • i=4,子串"abaca",长度为1时"a"和"a"相等,长度为2时"ab"和"ca"不等,所以 next[4] = 1。
  • i=5,子串"abacab",长度为2时前缀"ab",后缀"ab"相等;长度为3时前缀"aba",后缀"cab"不等,所以 next[5] = 2。
  • i=6,子串"abacaba",长度3时前缀"aba",后缀"aba"相等;长度4时前缀"abac",后缀"caba"不等,所以 next[6] = 3。

所以定义一下最终结果是:[0, 0, 1, 0, 1, 2, 3]。

但如果你用的是考试系统里的模板,看到next数组写法是[-1, 0, 0, 1, 0, 1, 2],也别慌,这是定义二的写法,它本质上是把前缀函数整体向右移动了一位,并在开头补了-1。假如题目明确说“next[i]为失配时跳转的位置”,你要会换算出这个结果。

2.3 手算next数组的快速技巧

很多同学觉得KMP难,是因为每道题都从推导式开始算,太慢。实际手算时可以用一个更直观的办法:直接看对称性,从长到短找最长相等前后缀。

比如计算到"abacaba"时,先看整个串有没有最长相等前后缀。明显首尾都是"aba",中间也刚好是同一个串的一部分,所以最长就是3。如果首尾相等,再往内看一级是否相等,多试两次就能确定。注意“真前后缀”不能等于整个子串本身,比如单字符子串最长相等前后缀一定是0。

一个小建议:考试时不要试图现场推KMP的完整代码,先把next数组的值速算出来,选择题就能拿分;如果编程题真遇到KMP,再套你背熟的模板。KMP的模板建议用C++或Python各写一遍,笔试时能更快适应环境。

vector<int> getNext(string p) { int m = p.size(); vector<int> pi(m, 0); for (int i = 1, j = 0; i < m; i++) { while (j > 0 && p[i] != p[j]) j = pi[j - 1]; if (p[i] == p[j]) j++; pi[i] = j; } return pi; }

上面这段计算的是前缀函数,也就是定义一。如果你要的是定义二,就先把pi计算出来,再转成[ -1 ] + pi[0..m-2]的形式。一定要看清题目给的是哪一种。

3. 从一道贪心题看区间调度与排序的底层思维

3.1 题目还原:课程安排最少教室数量

第二道编程题我印象里和“排课”有关。大致题意是:一堆课程,每门课有开始时间 start[i] 和结束时间 end[i],同一间教室同一时刻只能上一门课,问要安排所有课程最少需要多少间教室。

这其实是经典“会议室问题” (Meeting Rooms II)。放在猿辅导的场景里很自然,因为在线教育平台确实需要调度直播课和老师资源。题目没有绕弯子,看你能不能很快抽象出“区间重叠最大数”这个模型。

3.2 为什么贪心可行:排序的关键作用

第一次见这类题,可能会想用二维数组存所有区间,再双重循环统计重叠,但这样是O(n^2)的复杂度,数据量一大就超时。正确的解法是贪心 + 最小堆。

核心思路是:先把所有课程按开始时间排序,然后从左到右依次安排。使用一个小根堆维护当前已经占用教室的课程结束时间。遍历到新课时,如果堆顶课程的结束时间小于等于当前课的开始时间,说明最空闲的那间教室已经腾出来了,可以复用;否则说明当前所有正在上课的教室都无法空出来,需要新开一间教室。

为什么这里贪心是成立的?因为按开始时间排序后,我们每次处理的都是当前最早开始的课程,使用堆顶得到的是“最早结束的占用教室”。只要最早结束的教室都没法复用,那其他教室更不可能复用,所以必须新开教室。这个逻辑是局部最优递推到全局最优的经典例子,不需要回溯。

3.3 代码实现与边界验证

下面给出一个Python参考实现。注意区间端点重合的问题:一门课是 [1, 5),另一门是 [5, 6),它们不冲突,因为前一门在5点结束,后一门在5点开始。所以判断条件是start >= heap[0]就可以复用。

import heapq def minMeetingRooms(intervals): if not intervals: return 0 intervals.sort(key=lambda x: x[0]) heap = [] for start, end in intervals: if heap and start >= heap[0]: heapq.heappop(heap) heapq.heappush(heap, end) return len(heap)

这个代码简洁,但有几个易错点要自查:

  • 输入为空,直接返回0。
  • 排序时要按开始时间升序,不是结束时间。
  • 比较的是start >= heap[0],还是start > heap[0],取决于题目对“同时”的定义。如果结束的瞬间可以用来开始的下一门课,用>=;如果必须严格结束之后才能开始,也就是不能在同一时刻重叠,也通常用>=,因为结束时刻和开始时刻相同不算占用。如果题目明确说明两个课程在同一时刻边缘也算冲突,才需要用>。
  • 小根堆中存的是结束时间,不是教室编号。因为只有结束时间才能决定是否空闲。

笔试现场写完主体后,一定要自己造几个用例验证,比如intervals = [[0, 30], [5, 10], [15, 20]],期望答案是2。另外可以试一个全重叠的用例[[1, 4], [2, 5], [3, 6]],期望答案是3。这些边界测试在OJ系统里不会主动给你,漏了就容易错。

4. 动态规划:另一道笔试真题的递推与优化

4.1 题目场景化还原

第三道编程题在“算法岗三”里是一道动态规划,原题不一定完全出现在公开题库里,但考法很经典。我把场景稍微包装一下:假设有一串学习计划,第i天如果学习某门课,可以获得 values[i] 的收益,但不能连续两天学习,问一段连续日期内能获得的最大收益。

实际上这就是“打家劫舍”的变体,只是换了一个教育产品的壳。核心模型是:一个数组,每个元素可以选择拿或不拿,但不能同时拿相邻两个元素,求最大和。

4.2 状态定义与转移方程推导

动态规划的第一步永远是明确状态。设 dp[i] 表示从第0天到第i天能获得的最大收益。对于第i天,只有两种情况:

  • 选择学习第i天,那么第i-1天不能学习,收益是dp[i-2] + values[i]。
  • 不选择学习第i天,那么最大收益就是dp[i-1]。

因此转移方程为:

dp[i] = max(dp[i-1], dp[i-2] + values[i])

初始化要特别注意:dp[0] = values[0],因为只有一天时,学这一天就是最大收益;dp[1] = max(values[0], values[1]),前两天不能同时学,只能取更大的一天。如果数组长度只有1,直接返回 values[0];只有2,返回前两天的较大值。

这就是动态规划最典型的“选或不选”模型。很多同学能写出递归但写不出迭代,是因为没有先把状态定义清楚,导致边界一塌糊涂。笔试中时间紧,建议先把dp数组的长度、初始值写出来,再写循环。

4.3 空间优化与易错点

事实上,这个题不需要O(n)的dp数组,因为每次只依赖前两个状态,用两个变量滚动即可。比如:

def maxStudyValue(values): if not values: return 0 n = len(values) if n == 1: return values[0] prev2 = values[0] prev1 = max(values[0], values[1]) for i in range(2, n): cur = max(prev1, prev2 + values[i]) prev2, prev1 = prev1, cur return prev1

这个版本空间复杂度是O(1)。我在笔试时容易犯的一个低级错误是:写循环的时候忽略了n == 1的分支,导致在values[1]上越界。后来养成了习惯,凡是动态规划题,先处理空数组和长度为1的情况,再进入主逻辑。

动态规划的题目变化很多,但“选或不选”这个套路覆盖了非常多的考题,比如背包问题、最长上升子序列、股票买卖。备考时不需要把每种题都背一遍,而是要训练自己从题目描述里抽出“选择”和“限制条件”。看到“不能相邻”“最多一次”“不超过容量”这类关键词,立刻就能往状态转移上靠。

5. 选择题考点速记与避坑清单

5.1 机器学习与深度学习概念题

算法岗三的选择题里,机器学习概念占了不小的比例。这类题不涉及手推公式,但需要你对常见算法有正确的理解。比如:

  • 监督学习和无监督学习的区别:有标签是监督,无标签是无监督。
  • 过拟合的解决方式:增加正则、增加数据、简化模型、交叉验证,而不是盲目增大模型复杂度。
  • Bagging和Boosting的区别:Bagging是并行训练多个基模型再投票,Boosting是串行训练并关注前面样本的错误。
  • 激活函数 Sigmoid、ReLU、Tanh之间的特点,尤其是ReLU在正区间的梯度恒为1,能缓解梯度消失。

我建议你做一个速记表,把高频概念分类整理。下面是部分内容:

概念一句话记忆
过拟合训练集好、测试集差,模型太复杂或数据太少
正则化给损失函数加惩罚项,限制参数大小
交叉验证把训练集切分,轮流做验证集
KNN基于距离投票,不需要显式训练
K-Means无监督聚类,迭代更新质心
SVM找最大间隔超平面,核函数处理非线性
梯度下降沿负梯度方向更新参数
随机森林Bagging + 决策树

这些概念在笔试中出现频率很高,而且往往不是单纯问定义,而是给一个场景让你判断该用什么算法。比如“有一批没有标签的学生行为数据,想自动分成几类”,答案就是聚类算法,而不是分类算法。

5.2 数据结构与经典算法概念题

除了机器学习,数据结构和经典算法更是重头。印象里选择题有考到排序算法的稳定性、堆排序建堆过程、二叉树的遍历顺序、哈希冲突解决方案等。下面这张表是排序算法的核心结论,校招笔试百试百灵:

排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性
冒泡排序O(n^2)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(1)不稳定
插入排序O(n^2)O(n^2)O(1)稳定
快速排序O(n log n)O(n^2)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^2),但平均是O(n log n)。堆排序空间复杂度是O(1),但稳定性差。选择题如果要求“稳定且O(n log n)”,就要选归并排序。

另外,KMP、二分查找、Dijkstra这类算法也会作为选择题出现,但一般考复杂度或某个具体步骤,不会真的让你写完整代码。比如KMP的next数组计算,刚才已经详细讲过。二分查找则要注意循环条件是left <= right还是left < right,这直接影响是否漏判边界。

5.3 工程优化算法小题

这套卷子还出现了一些听起来像“从业务里摘出来的”算法概念题,比如粒子群算法原理、音频重采样算法、PID算法在某个系统中的作用、规则引擎Drools的Rete算法等。这类题其实并不要求你会推导,更多的是考察知识广度。

我当时对粒子群算法只知道一个大概:它模拟鸟群觅食,每个粒子根据个体最优和全局最优更新速度和位置。但选择题偏偏问的是“粒子群算法中每个粒子的速度更新受哪些因素影响”,如果你完全没听过,就只能靠排除法蒙。

这里给一个经验:准备算法岗笔试时不要只看纯数据结构,最好把常见优化算法和工程算法的“一句话原理”过一遍。比如卡尔曼滤波是递归状态估计、重采样是改变音频采样率、PID是比例积分微分控制、Rete是一种高效规则匹配算法。不需要会实现,但看到名字不能空白。

6. 限时答题的临场策略与复盘心得

6.1 时间分配建议

90分钟听起来不短,但三到四道编程题加上一堆选择题,时间其实很紧张。我当时的策略是:先快速过一遍所有题目,标记出哪些题有思路,哪些题需要更多时间。选择题尽量控制在25分钟以内,因为每道题平均不到两分钟,纠结太久只会挤占后面编程题的时间。

编程题分配上,第一道基础题建议不超过15分钟,第二道中等题不超过25分钟,第三道难题可以给到30分钟。如果某道题卡了超过10分钟没有新思路,先跳过,做完其他题再回头。很多OJ系统可以反复提交,所以即使不能保证满分,先把能拿的用例过了也是好的。

6.2 我踩过的坑和事后总结

复盘时我发现有几个问题特别值得提,基本都是“看起来不重要但实际丢分严重”的细节。

第一,KMP的next数组定义没有先确认。选择题里给的是“next[i]定义为最长相等前后缀长度”,我却按失配跳转的结果去算,差点选错。好在多看了一眼题干。笔试题里这类定义差异非常常见,一定要把题目给出的定义读清楚再动手。

第二,贪心排序的边界。课程时间排序时如果开始时间相同,需要额外考虑如何处理结束时间。有些变体会要求按结束时间排序,但经典会议室问题按开始时间排序就够。如果你一开始用的是双重循环的暴力法,即使逻辑正确,遇到大数据量也可能超时,所以最好直接用堆的解法。

第三,动态规划初始化遗漏。前面提到过dp[1]的处理,很多人在纸上推公式很顺,一到代码就忘。建议每道DP题都写一个固定模板:空数组、长度1、长度2,然后才是循环。

6.3 给后来人的刷题建议

如果你现在还在备考算法岗,我建议不要盲目追求刷题数量,而是分类刷题,每类题总结出通用模板。按优先级排序,校招笔试最常考的是:

  • 数组与双指针
  • 字符串(包括KMP、回文串)
  • 排序与堆
  • 贪心算法
  • 动态规划(背包、子序列、打家劫舍类)
  • 二叉树遍历与递归
  • 图论基础(Dijkstra、并查集、拓扑排序)

猿辅导这种互联网公司的算法岗,还有一点与纯后端开发不同:它会考一定比例的机器学习基础。所以你最好同时复习一下《统计学习方法》前几章的内容,重点是感知机、KNN、朴素贝叶斯、决策树、SVM、集成学习。不需要会推导所有公式,但要知道每种算法的适用场景、优势和劣势。

最后再分享一个小技巧。在线笔试环境下,输入输出格式经常出问题,尤其是Java和C++的同学,很容易卡在解析一行多组数据上。建议提前背熟三种常用输入模板:整数数组、字符串数组、二维数组。Python虽然方便,但要注意strip()去掉行尾空格,否则字符串比较会出现隐蔽bug。别小看这些细节,很多同学栽了跟头之后才意识到,算法题会做和能满分,中间隔着一堆工程习惯。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询