快手2019算法岗笔试B卷分析:从KMP到机器学习的高频考点 聊到算法岗校招笔试快手2019年春季那套算法B卷放在今天看依然很有嚼头。它考的并不是什么偏题怪题而是一个非常典型的算法工程师能力模型数据结构、经典算法、机器学习基础、工程视角一锅端。那会儿我刚好在帮团队做校招出题看到这套卷子时第一反应是“这出题人思路挺正”后来自己也把其中不少考点改头换面搬进了部门笔试题里。如果你正在准备算法岗笔试无论是快手还是其他大厂把这套题背后的知识体系吃透比死记一百道原题有用得多。1. 先看全局一套算法笔试卷到底在挑什么人1.1 笔试不是刷题比赛是能力筛子很多人误以为校招算法笔试就是比谁刷的LeetCode多其实出题人想筛的是另一层东西。一套合格的笔试试卷尤其是面向算法工程师岗位的B卷本质上不是想考倒你而是用有限的三四道编程题去探测你六个维度的能力代码基本功、算法模型识别、复杂度和边界意识、数学底子、机器学习的常识储备以及时间压力下的决策能力。快手这套2019年春招算法B卷正好卡在校招算法岗竞争开始白热化的那个时间点。当时的命题风格已经明显从“你会不会这道题”转向“你能不能想明白这道题背后的为什么”。比如一个考点可以包装成工程场景但内核还是你熟不熟悉KMP、排序、贪心、动态规划这些经典模型。换句话说题目只是外壳核心永远是那棵经典的算法知识树。1.2 B卷的考点分布与命题风格从标题和当时校友回忆的题目反馈来看这套B卷的考点覆盖大致是这么几块数据结构基础数组、链表、栈、队列、哈希表的使用与手写实现字符串算法KMP、Trie、字符串匹配类问题排序与搜索快速排序、堆排序、归并排序、二分查找及变体图论与贪心最短路、最小生成树、拓扑排序、贪心策略证明动态规划背包、区间DP、状态压缩DP的入门题机器学习基础聚类、KNN、损失函数、过拟合等概念题或小计算题如果你把热搜词里那一堆“粒子群算法原理”、“KMP next数组”、“数据结构排序算法”、“机器学习算法”、“深度学习算法”放到这个框架下看会发现它们不是零散的热点而是算法岗笔试的真实范围。也就是说这套卷子不是单纯考“你会不会写代码”而是考“你有没有完整的算法知识体系”。2. 硬核数据结构与高频算法逐个拆解2.1 字符串与KMP以“abacaba”为例手算next数组字符串匹配是笔试和面试中极其高频的考点而KMP算法基本是必问。2025年了还有不少同学看到KMP的next数组就头大其实它就是一个“失配时模式串跳到哪”的预计算表。网上关于next数组的版本多到能拍一部宫斗剧我建议你在笔试时先写清楚你用的是哪套定义别让阅卷人猜。拿热搜词里那个具体例子来说模式串 p abacaba我们手算两种常见定义。第一种是很多教材和C语言竞赛模板里用的next[i]表示当模式串第i位失配时下一次应该用模式串的第几个位置去跟主串比较下标从1开始。计算方式是把第i位之前即前i-1个字符的最长相等前后缀长度加1同时规定next[1] 0。next[1] 0前1个字符是a最长相等前后缀长度为0所以next[2] 1前2个字符是ab最长相等前后缀长度为0所以next[3] 1前3个字符是aba最长相等前后缀是a长度1所以next[4] 2前4个字符是abac最长相等前后缀长度为0所以next[5] 1前5个字符是abaca最长相等前后缀是a长度1所以next[6] 2前6个字符是abacab最长相等前后缀是ab长度2所以next[7] 3即 next [0, 1, 1, 2, 1, 2, 3]。第二种是刷题网站常见的定义next[i]表示模式串前i个字符或者说下标[0, i]这个子串的最长相等前后缀长度下标从0开始通常还会把next[0]设为-1。在这个定义下next[0] -1next[1] 0aba的最长相等前后缀长度1所以next[2] 1abac为0next[3] 0abaca为1next[4] 1abacab为2next[5] 2abacaba为3next[6] 3两种定义出来的数组数值不一样但本质都是前缀函数。笔试时如果题目明确给定了next的定义就按它的来如果没给我会在代码注释里写一句“next[i]表示前i个字符的最长相等前后缀长度”这样就避免了歧义。这个习惯帮我避开过不少隐性失分。2.2 排序算法全家桶复杂度、稳定性、以及“手写哪个”排序算法是算法岗笔试里最稳定的送分题也是区分“背过”和“真懂”的分水岭。很多题目不直接说“请实现快排”而是包装成“如何给大量URL按访问次数排序”、“如何用最小堆维护TopK”内核还是排序。我把最重要的几个排序算法整理成了一张对比表笔试前建议把这张表刻在脑子里。算法平均时间复杂度最好最坏空间复杂度稳定性冒泡排序O(n^2)O(n)O(n^2)O(1)稳定选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定插入排序O(n^2)O(n)O(n^2)O(1)稳定希尔排序O(n^1.3左右)O(nlogn)O(n^2)O(1)不稳定快速排序O(nlogn)O(nlogn)O(n^2)O(logn)不稳定堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定计数/桶/基数排序O(nk)O(nk)O(nk)O(nk)取决于实现这里特别想说一个细节快排的最坏情况是O(n^2)但笔试时很多同学容易忽略。如果题目给的数据范围是10^5你手写了一个固定选第一个元素做pivot的快排遇到基本有序的测试数据就会超时。我自己的习惯是手写快排时用随机pivot或者取首中尾三个数的中位数这样能大幅降低退化概率。堆排序虽然理论最坏也是O(nlogn)但常数偏大笔试中如果追求稳定输出归并排序往往是更稳妥的选择代价是额外O(n)空间。2.3 贪心与动态规划两兄弟的边界贪心和动态规划在笔试中经常成对出现因为它们的初看形态很像都是把一个大问题拆成子问题但一个只看眼前一个要遍历所有状态。识别方法其实很朴素如果每一步的局部最优能推出全局最优那大概率是贪心如果不行就需要DP。经典的贪心例子是区间调度给定若干个区间选出尽量多的互不重叠的区间。正确做法是按结束时间排序然后贪心地选最早结束的区间这也是为什么很多活动安排问题这么解的原因。笔试里如果让你证明贪心正确性你可以用“交换论证法”把最优解中的第一个区间替换成最早结束的区间证明不会变差。这套证明套路比直接写代码更让面试官眼前一亮。动态规划的话0-1背包是必练的基础题。状态定义dp[i][j]表示前i个物品放入容量为j的背包能获得的最大价值转移方程是dp[i][j] max(dp[i-1][j], dp[i-1][j-w[i]] v[i])空间优化成一维数组后内层循环必须从大到小遍历容量否则一个物品会被重复放进去for i in range(n): for j in range(m, w[i]-1, -1): dp[j] max(dp[j], dp[j-w[i]] v[i])笔试里DP的坑往往是初始化。我见过太多人把dp[0]初始化成0了事结果漏掉了“重量为0的物品可以多次选择”之类的边界条件。2.4 图论高频Dijkstra、二分图HK与最小生成树图论在算法B卷里属于中高难度段位但考来考去就是那几个经典模型。Dijkstra是最短路问题的主力核心思想是每次从未处理的节点中找出距离起点最近的点然后松弛它的邻边。它不能处理负权边的原因也在这里一旦某个点被标记为已确定最短距离后面再有负权边让它的距离变小时算法不会回头更新它了。用优先队列实现的Dijkstra复杂度是O((VE)logV)笔试时基本是这个版本。代码骨架大致这样import heapq def dijkstra(graph, start, n): dist [float(inf)] * n dist[start] 0 pq [(0, start)] while pq: d, u heapq.heappop(pq) if d dist[u]: continue for v, w in graph[u]: if dist[u] w dist[v]: dist[v] dist[u] w heapq.heappush(pq, (dist[v], v)) return dist二分图最大匹配也是热门考点热搜词里的“二分图hk算法”指的就是Hopcroft-Karp算法。它和朴素的匈牙利算法相比核心优化是先用BFS把多条不相交的增广路找出来再用DFS一次性进行多路增广复杂度可以压到O(E√V)。笔试中如果数据规模不大匈牙利算法足够但如果你想体现算法深度说出HK的优化思路会加分不少。最小生成树则绕不开Kruskal和Prim。Kruskal按边权排序后用并查集判断连通性Prim用优先队列维护当前连通块到外部的最小边。一个是边驱动一个是点驱动各自适合稠密图和稀疏图。3. 机器学习与深度学习考点不止是写代码3.1 聚类与KNN最容易被问的“基础题”算法工程师岗位的笔试题往往比纯后端开发岗位多了一层机器学习基础。K-Means和KNN是高频中的高频。K-Means的流程其实很简单随机选K个中心点迭代执行“分配样本到最近中心”和“重新计算中心”两步直到中心点不再变化或达到最大迭代次数。但笔试里喜欢考几个延伸点如何选K肘部法则、轮廓系数、如何初始化K-Means、如何避免空簇。关于KNN热搜词里有个问题问“knn算法的应用能力包括哪三个方面”我理解的三个主要应用方向是分类、回归、缺失值填补与异常检测。分类就是投票表决回归是取邻近样本的均值或加权均值缺失值填补本质上是利用邻近样本估计缺失项异常检测则是看样本距离K个近邻的距离是不是异常大。这里有一个容易翻车的点K值太小容易过拟合比如K1时决策边界非常曲折K值太大又会把远距离样本也纳入进来导致欠拟合。另一个是距离度量欧氏距离、曼哈顿距离、余弦相似度适用的场景不一样文本场景通常用余弦相似度数值特征场景用欧氏距离更多。3.2 从损失函数到优化器公式推导要过关机器学习部分除了概念题还会有一些“给你一个公式让你解释为什么”的题。交叉熵和KL散度的关系是个很好的例子KL(P||Q) H(P, Q) - H(P)也就是说KL散度等于交叉熵减去真实分布的熵。因为在训练时真实分布P是固定的所以最小化交叉熵等价于最小化KL散度。这种“等价性”推导往往是笔试简答题的得分点。过拟合和正则化也是常客。L1正则化会让参数稀疏因为它等价于在拉普拉斯先验下做最大后验估计L2正则化会让参数整体变小等价于高斯先验。这些不是死记硬背而是能推导的。我建议在准备阶段把线性回归、逻辑回归、SVM、决策树、随机森林、XGBoost的核心思想都整理成一段话每个算法能说出“它解决了什么问题、核心步骤是什么、和上一个算法的区别是什么”。XGBoost在热搜词里也出现了它之所以比传统GBDT强核心在于两处目标函数用了二阶泰勒展开相比一阶信息收敛更快显式地在目标函数里加入了正则项抑制了过拟合。这些点即使笔试不考面试时被问到的概率也很大。3.3 算法工程师的数学底子算法笔试里很多题表面是编程题底层其实是数学题。最典型的如概率题抛硬币、抽卡、生日悖论、贝叶斯公式。线性代数方面特征值和特征向量的含义、矩阵乘法的计算量、对称矩阵的性质都是高频。微积分方面链式法则、梯度方向、偏导数的计算是必考基本功。我记得有一年某大厂笔试出了道题让你用代码模拟蒙特卡洛方法估算圆周率这其实就是在考你对大数定律和随机抽样的理解。数学底子不是临时抱佛脚能补上的建议提前把三个核心板块过一遍概率论里的全概率公式和贝叶斯公式、线性代数里的特征值分解和SVD概念、微积分里的梯度和链式法则。4. 容易被忽略的“工程向”算法考点4.1 信号与数值计算重采样、图像锐化与PID这类题目往往披着“音频算法工程师”“图像算法工程师”“控制算法工程师”的马甲但出现在算法B卷里也不奇怪。热搜词里那么多“音频重采样算法”“图像锐化的拉普拉斯算法”“PID算法”说明这些方向在真实笔试中确实出现过。音频重采样比如把44100Hz的音频转成16000Hz最朴素的做法是线性插值但会产生谐波失真。工程上常用多相滤波或sinc插值本质是用一个低通滤波器把原始信号中超过目标采样率一半的频率分量滤掉防止混叠。笔试题如果考这个多半是问“为什么直接抽样会失真”而不是让你写完整的滤波器实现。图像锐化的拉普拉斯算子核心卷积核长这样0 -1 0 -1 4 -1 0 -1 0这个核计算的是二阶导数能提取出图像中的高频分量。锐化的做法是原图减去拉普拉斯算子提取的边缘或者等价地使用类似0 -1 0; -1 5 -1; 0 -1 0的核直接做卷积。笔试里考卷积核运算的题目其实就是让你手算一个3x3的卷积结果边界像素怎么填充zero padding、replicate padding也要提前想清楚。PID算法在控制工程师岗位的笔试里就是核心题。位置式PID的公式是u(k) Kp * e(k) Ki * Σe(i) Kd * (e(k) - e(k-1))增量式PID则输出的是控制量的增量好处是不会累计误差且切换时冲击小。调参经验是先调Kp让系统响应快再加Ki消除静差最后加Kd抑制超调。这个顺序笔试简答题和实战都是通用的。4.2 搜索与优化粒子群、模拟退火、卡尔曼滤波这类算法是“全局优化算法”的代表笔试里不会让你手写完整实现但会问你“它和梯度下降有什么区别”“关键公式里每个参数的意义是什么”。粒子群算法的速度更新公式是个经典考点v w * v c1 * r1 * (pbest - x) c2 * r2 * (gbest - x)其中w是惯性权重c1是认知学习因子c2是社会学习因子r1和r2是[0,1]随机数。pbest是粒子自身历史最优gbest是群体历史最优。这套公式可以解释成“粒子朝自己见过的最好位置和群体见过的最好位置两个方向的加权平均移动”。模拟退火的核心是Metropolis准则如果新解比当前解好一定接受如果新解更差以exp(-ΔE/T)的概率接受。温度T越高接受劣解的概率越大这样算法初期能跳出局部最优后期温度降低逐渐收敛。笔试里问你“为什么模拟退火能跳出局部最优”答案就是这个概率接受机制。卡尔曼滤波则常用于融合传感器数据核心是预测和更新两个步骤。预测阶段由状态转移矩阵算出先验估计和协方差更新阶段用卡尔曼增益在观测值和预测值之间加权。考概念题时只要能把“预测-更新”循环讲清楚并且说出卡尔曼增益是权衡“预测误差”和“观测误差”的比例系数基本能拿分。4.3 安全与推荐方向的跨领域考点SM系列、BM25、Rete算法岗的边界比很多人想象的宽一套B卷里混进安全、推荐、规则引擎的考点也不罕见。热搜词里出现了“sm2、sm3、sm4和zuc算法”这是国密算法的四件套。SM2是非对称加密基于椭圆曲线SM3是哈希算法输出256位摘要SM4是分组对称加密分组长度128位ZUC是流密码算法主要用于移动通信。笔试中如果出现这类题通常考的是“这四者分别属于哪种密码体制”以及基本的安全强度。推荐算法方向BM25是经典的相关性打分函数。它的公式长这样score(D, Q) Σ IDF(qi) * (f(qi, D) * (k1 1)) / (f(qi, D) k1 * (1 - b b * |D| / avgdl))理解它的关键是词频越高分越高但并不是线性增长因为k1会限制词频的边际贡献文档越长词频的贡献会被稀释b参数控制长度惩罚的强度。笔试如果考推荐召回BM25和TF-IDF的区别是高频简答题。规则引擎Drools里的Rete算法出现在算法题里会让人意外但它的本质是一个高效的模式匹配算法核心是构建α网络和β网络α网络负责对单个事实做条件过滤β网络负责在不同条件之间做连接和共享中间结果。笔试如果考到重点是“为什么要用Rete”——因为规则引擎往往有很多规则共享相同条件Rete可以把公共子表达式缓存起来避免大量重复匹配。5. 备考路线从零到算法岗笔试通关5.1 按优先级排定刷题计划算法岗笔试备考最忌讳一上来就刷难题。我建议按照下面这个优先级来分配精力这也是我给多个学弟学妹规划过的路线第一优先级数组、链表、栈、队列、哈希表、字符串第二优先级二叉树遍历、二叉搜索树、堆、排序、二分查找第三优先级回溯、贪心、动态规划入门背包、子序列、区间第四优先级图论DFS/BFS、拓扑排序、最短路、最小生成树、并查集第五优先级机器学习基础概念、经典算法推导每一层没有吃透就不要急着往下一层走。比如递归和回溯没练熟直接上树的题目会非常痛苦。我见过太多人跳过基础一上来就啃状态压缩DP结果信心崩盘。另一个关键点是刷题要按“知识块”来不要随机跳题。一个知识块集中刷20道左右才能真正形成条件反射。比如KMP不要只刷一道模板题而是把字符串匹配类问题集中起来顺便把Trie树和AC自动机了解一遍。5.2 笔试现场的时间分配与做题顺序笔试现场的做题顺序决策得好能多拿不少分。我的习惯是拿到卷子先花5分钟把全部题目扫一遍不要立刻上手写代码。扫题时在草稿纸上标记每道题的题号和预估难度然后按“最简单、最有把握”的顺序开始写。笔试不是面试没有人会因为你跳过难题指责你分数最大化才是唯一目标。一般一套算法B卷是3到4题时间在90分钟到150分钟之间。最简单的题控制在20到30分钟内AC中档题40分钟内AC剩下时间留给难题。如果一道题想了20分钟一点思路都没有立刻写一个暴力解法先拿部分分再考虑优化。暴力解法拿到的分远比空着等灵感值钱。写代码时我习惯先写一个逻辑清晰的暴力版本并确保通过样例然后在这个基础上优化。这样即使后面优化失败也已经交了保底答案。优化有思路时优先改复杂度瓶颈比如把O(n^2)改成双指针、哈希表或二分而不是去抠常数优化。5.3 个人心得练成“复杂度直觉”刷题到后期真正的分水岭不是背了多少模板而是形成一种“复杂度直觉”看到数据范围能立刻推测出这道题应该用哪种复杂度的算法。这里有个简单参考表数据规模10^8以上基本只接受O(n)或O(nlogn)很可能是找规律、滑窗、二分数据规模10^5到10^6O(nlogn)是主流排序、堆、线段树、扫描线数据规模10^4左右O(n^2)勉强能过可以尝试两层循环或者区间DP数据规模100到500O(n^3)可以考虑比如Floyd、三重循环数据规模20到30大概率是状态压缩DP或BFS剪枝有了这个直觉你看到题目时不会盲目设计算法而是先根据数据范围倒推复杂度需求。这种做法在笔试里特别能控制时间也特别能反映一个人的工程素养。6. 常见问题与避坑经验速查6.1 笔试高失分点不是不会做是坑太多我阅过不少笔试代码发现很多同学挂在“不会做”之外的地方。这些问题很可惜整理成速查表每一条都是真实的常见丢分点常见问题原因解决办法超时但不知道原因没根据数据范围算复杂度先看数据范围再倒推目标复杂度数组越界、空指针边界条件没测试对空数组、最小长度、最大长度分别自测递归栈溢出递归深度过大改成迭代或自底向上DP快排退化到O(n^2)每次选固定pivot随机pivot或三数取中整数溢出int不够放结果中途用long或大数类型样例过了但0分输出格式与题目不一致读题时确认输出格式、换行、空格本地调试信息没删调试语句污染输出提交前全局搜索print/debug其中“样例过了但0分”最让人崩溃多数是输出格式问题。我自己的习惯是写完代码后先跑一遍题目给的样例再构造两个边界数据最小输入、最大输入最后再跑一个极端情况的输入比如空串、只有两个节点、数据全部相同。6.2 从一场笔试看算法岗的长期能力积累这套快手2019年春招算法B卷我后来也拿来当过部门笔试的参考出题思路一脉相承。如果你想在校招中拿到算法岗的入场券不要指望临时抱佛脚更不要指望背题能覆盖所有考题。真正值得投入时间的是把每个经典算法的“为什么”想明白把数据结构和数学底子打牢然后通过大量练习形成稳定的代码输出能力。我自己到现在写代码还保留一个习惯动笔前先在注释里写清楚复杂度目标再写实现。笔试考的从来不只是你会不会做这道题而是你在AC之前有没有想清楚它为什么这么做。这个习惯工作之后比考试本身更值钱。以后再有人问算法岗笔试怎么准备我会建议他先别急着打开题库先花一周时间把这套算法知识体系里最基础的几个模型彻底嚼碎再动手写题。有了地基楼才能盖得稳。