尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

校招算法笔试高频考点全拆解:KMP、堆排序、Dijkstra与快速幂

校招算法笔试高频考点全拆解:KMP、堆排序、Dijkstra与快速幂 算法笔试基本是所有互联网大厂校招的第一道硬门槛。我自己带过不少应届生也帮人做过很多次模拟面试回头看小红书2020校招算法笔试题卷一会发现它很典型既有基础数据结构也有经典算法设计还穿插了少量机器学习概念题。这套卷子的命题风格在内容平台里很有代表性适合准备算法岗、后端岗的同学重点研究。无论是KMP的next数组还是堆排序、Dijkstra、快速幂都是后面面试环节反复出现的熟面孔。这篇文章我就按卷面常见模块把涉及的核心考点逐一拆开顺带把代码模板和踩坑点一起整理出来。1. 卷面整体设计与高频考点分布1.1 命题结构与难度曲线这类校招算法试卷通常不是单纯考“会不会背模板”而是考察三件事基础数据结构掌握程度、算法设计能力和代码落地能力。小红书这份卷一整体走的是“选择题保广度、编程题保深度”的路线选择题覆盖排序、哈希、字符串、图论、机器学习基础编程题则集中在DFS/BFS、动态规划、贪心、图最短路这类经典题型上。从难度曲线来看一般遵循“平缓进场、中途爬坡、末尾拔高”的节奏。前面几道选择题相对友好比如时间复杂度的判断、排序算法稳定性、哈希冲突处理方式只要基础扎实基本能拿分。中段开始出现KMP next数组、堆排序、TopK这类需要动手推导的题目这里会筛掉一部分只背结论不理解原理的人。最后则是复合型编程题常见组合包括“Dijkstra 状态压缩”“区间DP 贪心优化”“矩阵快速幂 大数取模”这种题考察的是你在压力下把思路转化为可运行代码的能力。1.2 高频考点和分值占比参考以这套卷一为样本结合同类内容平台公司的出题习惯我整理了一张考点频率表方便你对照着做复习规划。考点模块常见题型出现频率建议优先级排序算法复杂度、稳定性判断、手写快排/归并很高必拿分字符串算法KMP next数组、回文串、Trie树高必拿分数据结构应用哈希冲突、单调栈、堆高必拿分图论算法Dijkstra、BFS/DFS、拓扑排序中高重点突破动态规划背包、区间DP、状态压缩DP高重点突破贪心算法区间调度、跳跃游戏中掌握套路数学与位运算快速幂、最大公约数、组合数中高易拿分机器学习基础粒子群算法、KL散度、正则化中算法岗单独准备实际做题的时候选择题要控制时间能快速判断的就不要反复推演。编程题则建议先读全部题目挑最稳的题先写别一上来就死磕压轴题把时间耗在难题上导致简单题没写完整这个教训我见过太多次了。2. 字符串与基础数据结构KMP next数组计算与哈希细节2.1 KMP next数组的手算思路与代码实现字符串相关题目里KMP算法几乎是必考内容。热词里出现了一个很经典的例子模式串pabacaba让你求next数组。首先解释一下next数组的常见定义在大多数教材和校招题里next[i]表示“模式串前 i1 个字符组成的子串中最长相等真前后缀的长度”。注意是“真前后缀”也就是不能取整个子串本身。对abacaba逐个位置手算下标子串最长相等真前后缀next值0a无01ab无02abaa13abac无04abacaa15abacabab26abacabaaba3所以next [0, 0, 1, 0, 1, 2, 3]。有了这个数组匹配时如果主串和模式串在位置 j 发生失配模式串指针可以直接跳到next[j]的位置继续比较而不是回到开头这就是KMP能做到 O(nm) 时间复杂度的原因。代码实现用Python写的话核心是维护一个“当前已匹配前缀长度”变量 jdef build_next(p): n len(p) nxt [0] * n j 0 for i in range(1, n): while j 0 and p[i] ! p[j]: j nxt[j - 1] if p[i] p[j]: j 1 nxt[i] j return nxt很多人写错KMP是因为边界条件混乱尤其j nxt[j-1]这里本质是“递归地向前找更短的相等前后缀”。建议平时练习时在纸上至少手算三组模式串比如aaaa、ababc、abacaba把数组每一步的跳转逻辑跑通考场上就不会慌。2.2 哈希冲突、单调栈与栈的隐藏考法基础数据结构不会单独给一道大题但选择题里非常爱考。哈希这块链地址法、开放寻址法、再哈希法的区别要能说清。比如用链地址法时如果哈希函数选得不好某个桶链表过长平均查找时间会退化到 O(n)。这也是为什么C的unordered_map在桶长度超过阈值后会把链表转成红黑树本质就是防止哈希冲突导致的性能劣化。单调栈是这些年出现频率非常高的数据结构题。典型题目是“给定一个数组求每个元素右边第一个比它大的元素”用普通做法是 O(n²)用单调栈可以优化到 O(n)。核心思路是维护一个从栈底到栈顶递减的栈遍历数组时当前元素不断和栈顶比较只要栈顶小于当前元素就弹出并记录答案。为什么这个做法是对的因为对于弹出的元素它右边第一个更大的元素一定就是当前这个元素栈内更小的元素已经没有机会成为答案了及时弹出才能保证每个元素最多入栈出栈一次。栈还有一个经典考点是括号匹配这个相对简单重点是注意栈为空时遇到右括号的情况以及遍历结束后栈是否为空。这类题目在笔试里属于“送分题”但前提是你平时就把代码写熟而不是到了考场上临场推理。3. 排序与堆TopK问题的高效解法与易错点3.1 排序算法复杂度与稳定性速查排序算法几乎是每套校招笔试的必考项。选择题常问“哪个排序算法不稳定”“归并排序的空间复杂度是多少”“快排最坏情况下时间复杂度是多少”需要把这张表记牢。排序算法平均时间复杂度最坏时间复杂度额外空间稳定性冒泡排序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)不稳定选择题之外手写快排也是编程题高频。快排最核心的partition函数最容易错的点一是边界条件比如left right还是left right二是基准元素的选择很多同学默认选第一个元素对于几乎有序的数组会退化成 O(n²)所以正规代码建议用随机下标或三数取中。实际笔试中如果环境允许我一般会用三数取中避免最坏情况。3.2 堆排序与海量数据TopK的两种实现TopK问题是“数据结构应用”和“海量数据处理”的交汇点整套卷子里出现概率很高。题目描述通常是“给出一个长度为 n 的整数数组找出最大的 k 个数”n 非常大不能直接全部排序。第一种思路是维护一个大小为 k 的小根堆。遍历数组时如果堆未满就插入如果堆已满且当前元素比堆顶大就弹出堆顶再插入。因为堆顶是堆中最小的元素只要当前元素比它大说明堆顶必然不是前 k 大可以被替换。import heapq def top_k(nums, k): heap [] for x in nums: if len(heap) k: heapq.heappush(heap, x) elif x heap[0]: heapq.heapreplace(heap, x) return heap这段代码的时间复杂度是 O(n log k)空间复杂度 O(k)。对于海量数据没法一次性读入内存的场景这个方案更实用可以分批读入数据流每来一个元素就更新堆。第二种思路是快速选择QuickSelect平均时间复杂度 O(n)但最坏情况 O(n²)。代码写起来比堆麻烦笔试中如果数据规模不大我更推荐优先写堆的方案思路清晰、代码短、不容易出错。排序与TopK这道题常见的错因是把“找前 k 大”写成了大根堆。大根堆的堆顶是堆中最大的元素如果用它维护前 k 大新元素进来后你无法判断该淘汰谁。记住找前 k 大用小根堆找前 k 小用大根堆这个方向一定不能搞反。4. 图论与搜索Dijkstra堆优化与剪枝实战4.1 Dijkstra堆优化从朴素到优先队列图论题中最常见的单源最短路径就是Dijkstra。朴素版Dijkstra每次从未确定最短路的点中找到距离最小的点需要 O(V) 的扫描整体 O(V²)。笔试中 n 如果到 10^5 级别朴素写法必然超时所以必须用优先队列最小堆优化。Dijkstra堆优化的核心思想是每次从堆里取出当前距离最小的点用它去松弛邻接边。如果松弛后某个点的距离变小了就把新的距离和点的编号一起压入堆。import heapq def dijkstra(n, edges, start): graph [[] for _ in range(n)] for u, v, w in edges: graph[u].append((v, w)) graph[v].append((u, w)) 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]: nd d w if nd dist[v]: dist[v] nd heapq.heappush(pq, (nd, v)) return dist需要注意的是if d ! dist[u]: continue这一行它用来跳过堆里的“过期元素”。因为同一个点可能被压入堆多次如果堆顶弹出的距离已经不再是当前最短距离说明这个点已经被更好的路径更新过了直接跳过即可。很多同学忘写这行轻则多算几轮重则死循环。4.2 BFS最短步数与剪枝技巧图论题目还有一类是BFS求最短步数比如走迷宫、单词接龙。BFS天然适合无权图每层扩展一步第一次到达终点时就是最短步数。实现时要注意标记访问状态避免同一个点被重复入队否则会指数级膨胀。DFS则经常配合剪枝使用。热词里有“剪枝算法”笔试中对复杂搜索题特别重要。举一个数独的例子如果做纯DFS会尝试很多明显不可能的路径。常见剪枝包括可行性剪枝当前放法是否违反数独规则、最优性剪枝当前成本已经大于已知最优解就提前返回。还有一个实用技巧是先选择候选数字最少的空格填也就是“优先选择分支数最少的位置”这样能大幅减少搜索空间。二分图相关题目在热词里也出现过笔试中主要考察染色法判断二分图。思路很简单从任意点开始把相邻点染成不同颜色如果发现相邻点颜色相同说明存在奇环不是二分图。HK算法属于进阶内容如果目标岗位不是图形学或大规模匹配相关方向能说出染色法原理就够了。5. 动态规划、贪心与快速幂必拿分题型的实现细节5.1 01背包倒序遍历的原因与区间DP套路动态规划在校招卷里的地位非常高尤其01背包属于“必须熟练掌握到肌肉记忆”的题。核心问题描述有 n 个物品每个物品有重量 w[i] 和价值 v[i]背包容量为 C问能装下最大价值是多少。二维转移方程很直观dp[i][c] max(dp[i-1][c], dp[i-1][c-w[i]] v[i])。面试时更常写一维滚动数组def knapsack(weights, values, capacity): dp [0] * (capacity 1) for i in range(len(weights)): for c in range(capacity, weights[i] - 1, -1): dp[c] max(dp[c], dp[c - weights[i]] values[i]) return dp[capacity]这里的关键是内层循环必须倒序。原因是滚动数组压缩掉了“物品维度”如果正序遍历容量dp[c - weights[i]]可能已经包含了当前物品导致同一件物品被选多次。倒序则能保证每次更新都基于上一轮的结果也就是每个物品最多选一次。如果是完全背包问题把内层循环改成正序遍历即可这一点笔试常考。区间DP也是高频。典型题是“石子合并”或“最长回文子序列”。区间DP的通用框架是先枚举区间长度再枚举左端点然后枚举区间分割点。做题时需要注意枚举顺序长度从1到n不能直接按左右端点顺序遍历否则转移依赖的小区间可能还未计算。5.2 快速幂的二进制原理与矩阵扩展快速幂这个知识点热词里也出现了它解决的核心问题是“计算 x^n 并对 mod 取模”。n 如果到 10^18 级别直接循环乘 n 次完全不可行。快速幂利用二进制分解把问题复杂度降到 O(log n)。原理其实很简单n 用二进制表示比如 n13 就是二进制 1101也就是 841所以 x^13 x^8 × x^4 × x^1。代码实现时每次把指数右移一位同时把底数平方如果当前位是1就把结果乘上当前底数。def fast_pow(x, n, mod): x % mod res 1 while n: if n 1: res res * x % mod x x * x % mod n 1 return resC版本也很常用long long fast_pow(long long x, long long n, long long mod) { x % mod; long long res 1; while (n) { if (n 1) res res * x % mod; x x * x % mod; n 1; } return res; }快速幂还能扩展到矩阵快速幂用来在 O(log n) 时间内求斐波那契数列第 n 项。笔试如果出现数值非常巨大的斐波那契题基本就是考这个。矩阵快速幂的模版比标量版本多一个矩阵乘法函数平时建议提前备好考试时直接套。贪心算法这里顺带提一句最常见的错误是“直觉上觉得对就写”。贪心题需要证明“局部最优能推出全局最优”比如区间调度问题要先按结束时间排序这个排序依据就是证明核心。如果一时间想不出证明先写一个暴力和贪心对拍一下至少能帮你排除大部分错误思路。6. 机器学习算法基础粒子群、KL散度与常考概念6.1 粒子群算法PSO原理与参数细节这一节对算法岗特别重要。热词里专门出现了“粒子群算法原理”说明机器学习相关概念在校招算法卷里有一定占比。粒子群算法是一种群智能优化算法模拟鸟群觅食行为。每个粒子代表解空间中的一个候选解迭代过程中粒子根据“个体历史最优”pbest和“群体历史最优”gbest来更新自己的位置。速度更新公式v_new w * v_old c1 * r1 * (pbest - x) c2 * r2 * (gbest - x)位置更新公式x_new x v_new其中 w 是惯性权重控制粒子沿原来方向飞行的程度c1、c2 分别是自我认知和社会认知系数r1、r2 是 [0,1] 之间的随机数。w 越大全局搜索能力越强w 越小局部搜索越精细但容易陷入局部最优。笔试考法通常是给一个简单目标函数比如最小化f(x) x1^2 x2^2要求用粒子群算法写出迭代几轮后的结果。这种题只要记住公式按步骤代入就能拿分。另一种考法是选择题问“下列哪个参数能增强全局搜索能力”答案一般是“增大惯性权重”或“增大 c2 社会认知系数”。6.2 KL散度、ELBO以及模型评估高频点KL散度是衡量两个概率分布差异的指标定义是D_KL(P||Q) Σ P(x) log(P(x)/Q(x))。它不满足对称性也就是D_KL(P||Q)不等于D_KL(Q||P)所以严格来说它不是距离度量。这一点选择题考过很多次。变分推断里的ELBO也是热词。目标是近似后验分布p(z|x)直接求很难算于是找一个简单分布q(z)去逼近。最小化KL(q(z)||p(z|x))是不可行的因为里面包含难算的后验但可以最大化ELBO也就是E_q[log p(x,z)] - E_q[log q(z)]。最大化ELBO本质上等价于最小化那个KL散度因为两者之和是常数log p(x)。这部分内容不需要你像数学系那样推公式推到底但对概念理解要准确。算法岗笔试选择题常考正则化、特征缩放、ROC曲线和AUC含义、样本不均衡的处理方式。比如L1正则化为什么能产生稀疏解是因为L1范数在零点不可导优化过程中更容易把参数压到0。这类题目的特点是“背了就能拿分”但前提是你真的把概念和公式联系起来理解而不是死记。7. 考场实战编程题代码规范、时间分配与常见坑7.1 输入输出模板与在线OJ注意事项校招笔试平台和LeetCode这种本地写函数的模式不太一样很多需要用标准输入输出。比如牛客风格的题目需要你从stdin读取数据处理完再输出。平时如果只刷LeetCode很容易在笔试时卡在输入输出上白白丢分。Python端的通用模板import sys def solve(): data sys.stdin.read().split() idx 0 n int(data[idx]) idx 1 nums list(map(int, data[idx:idx n])) # 业务逻辑 print(ans) if __name__ __main__: solve()C端通常就是cin加cout注意关闭同步以加速#include iostream using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin n; // 业务逻辑 return 0; }在线做题时还有一个容易被忽略的点不要过度依赖本地IDE的错误提示。很多笔试平台只告诉你“通过率0%”不会告诉你哪一行报错。写代码的时候变量命名清晰一点逻辑分支尽量精简至少有利于自己排查。7.2 时间分配和调试技巧一套卷子如果给120分钟我的建议是前30到40分钟搞定所有选择题剩下时间都给编程题。编程题不要按题目顺序写先花2分钟浏览全部题面把“稳拿分”的题标记出来优先做。一般第一道编程题会比较简单比如“给一个数组求某个统计量”这类题要在15分钟内一次写对给后面的难题留出缓冲。考场上debug最有效的办法是准备几组自测样例覆盖边界情况。常见的边界包括数组长度为1、元素全相同、输入为空、目标值不在数组中、容量小于最小物品重量等。KMP这类字符串题可以拿题目给的模式串手算一遍next数组和代码输出对比。如果发现结果不一致优先怀疑下标边界。还有一个很实用的习惯编译通过后先跑一遍最朴素的暴力解法再跑优化版本对比结果是否一致。如果一致大概率正确如果不一致说明优化逻辑里有隐藏bug。这个对拍习惯我建议从平时刷题就开始养成。8. 高频失误与备赛路线建议8.1 校招笔试题常见错误速查表我把这些年批改笔试答案时看到的高频失误整理成一张表你对照自己平时的代码习惯检查比盲目刷题有效得多。问题现象根本原因正确做法快排在有序数组上超时基准元素固定取第一个随机基准或三数取中TopK结果正好反了用大根堆找前k大找前k大用小根堆Dijkstra死循环或TLE没有跳过过期堆元素弹出时判断d ! dist[u]就跳过KMP输出next数组不对没有区分“最长相等前后缀”和“跳转位置”两种定义先确定题目采用哪种定义01背包结果偏大内层容量正序遍历必须倒序遍历BFS队列无限膨胀没有标记访问状态入队前设置visited快速幂结果溢出C中使用int相乘使用 long long 并且在每一步取模8.2 针对这套卷子的复习节奏建议如果你现在离笔试还有一个月我的建议是按照“基础题全对、进阶题会写、压轴题有思路”的目标来分阶段准备。第一周和第二周主刷LeetCode Hot 100 和剑指 Offer重点放在数组、链表、二叉树、字符串、动态规划这些模块。每道题做完之后花一分钟在笔记上写下时间复杂度和空间复杂度这个习惯非常重要因为选择题直接考复杂度。第三周做专题强化针对热词里反复出现的KMP、快速幂、Dijkstra、堆排序这类题型每个专题至少手写一遍完整代码不要复制粘贴要能默写出来。最好在纸上手写代码模拟笔试场景你很快会发现“看懂了”和“能默写”完全是两回事。最后一周进入整套模拟找牛客或赛码上的公司真题按考场的时限完整做一遍。这个时候重点是练节奏选择题不要犹豫超过2分钟编程题先易后难。做完之后不只看对错还要复盘哪些题是知识点不会哪些是边界没处理好哪些是时间分配不合理分类记录考前重点看这些记录。机器学习方向的备赛不太一样建议同时看“百面机器学习”或统计学习方法里关于模型评估、损失函数、正则化、聚类、降维这些章节。算法岗笔试的ML题通常不会太深但概念覆盖面广最划算的做法是把高频概念以“名词解释 公式 优缺点”的形式整理成卡片每天过一遍。最后说点个人体会。我带过的学生里笔试失分最多的往往不是“不会做”而是“会但没写对”——要么没卡边界要么时间不够用要么输入输出格式不规范。这套小红书2020校招算法笔试题卷一最大的价值不是让你背题而是用它验证自己“从思路到代码”的完整链路。把基础算法练到肌肉记忆再配合一两套限时模拟校招笔试这一关基本就稳了。
返回列表