1. 复试算法到底考什么:先想明白边界才能对症下药
在正式复盘之前,我想先说说最容易被忽视的一件事:复试里的算法,和竞赛刷题、期末考试的算法并不是同一个东西。复试算法考察的是你对基础数据结构和经典算法的理解深度、代码实现能力、以及临场表达思路的清晰度,而不是追求刁钻的难题解法。这一点想明白之后,我的整个复习策略都跟着调整了。
1.1 笔试机试、面试手撕、口头问答三个场景的分层
我把复试算法的考察方式拆成了三条线,分别对应不同的准备重点:
- 笔试/机试:通常是限时的代码题,环境可能是白板在线编辑器,也可能是本地IDE提交。考察重点是能不能在规定时间内跑通正确代码,以及边界情况是否覆盖全面。这时候比拼的是代码熟练度,而不是思路有多惊艳。
- 面试手撕代码:老师可能会当场给你一道题,让你在白纸或白板上写出来。此时老师真正关注的是你的思考过程,先别急着写,先说思路,说复杂度,再落笔。写得慢不要紧,思路混乱是大忌。
- 口头问答:问概念、问原理、问“为什么”和“如果不这样会怎样”。比如“为什么快排最坏复杂度是O(n^2)”“哈希冲突怎么解决”“动态规划和无脑暴力的本质区别在哪”。
这三条线对应的时间分配完全不一样。我当时给自己定的比例是:机试占50%精力,面试手撕占30%,概念问答占20%。如果你报考的院校机试占比极高,这个比例还要再偏压。
1.2 核心考察内容的优先级排序
把近三年我能找到的复试经验帖和回忆题扫了一遍之后,我提炼出一个大概的优先级列表,供参考:
| 优先级 | 内容模块 | 理由 |
|---|---|---|
| S级 | 排序算法(快排、归并、堆排)、二分查找、DFS/BFS、递归 | 几乎所有学校都考,且常作为手撕题出现 |
| A级 | 动态规划经典模型(背包、最长递增子序列、编辑距离)、贪心、链表/树操作 | 高频考点,容易从机试延伸到面试问答 |
| B级 | 图论(最短路、并查集、拓扑排序、最小生成树)、KMP、哈希 | 根据学校偏好看情况准备,计算机科班强烈建议准备 |
| C级 | 高级数据结构和复杂算法(线段树、Tarjan、A*、平衡树等) | 有竞赛经历或报考方向上需要可以加分,否则量力而行 |
这里特别提醒一下:很多同学把大量时间耗在炫技类算法上,结果复试被一道链表反转问得语无伦次。复试算法首先要保证“基础题不失误,常规题思路清晰,拔高题能写多少写多少”。
1.3 从热搜词反推复试算法的常见范围
我顺手整理了一些算法热搜词,发现它们其实能很好地覆盖复试算法的考察面:归并排序、堆排序、冒泡排序、贪心算法、KMP、A*算法、Tarjan算法、弗洛伊德算法、匈牙利算法、滑动平均滤波、PID算法、粒子群算法、随机森林、线性回归、YOLO、强化学习、BPTT等。
这些词里,前一半是计算机基础复试的高频点,后一半更像是读研阶段工程和科研方向会触达的算法。我的感受是:复试不只是考“你会不会写代码”,更是考“你有没有持续学习算法的能力”,所以基础算法是硬通货,而偏向工程和科研的算法哪怕只是了解原理,也会让老师觉得你有主动扩展的意识。后面我会专门写一节如何把这些扩展话题沉淀成自己的亮点。
2. 复习路线的三个阶段:从“看得懂”到“写得对”
确定范围之后,我给自己排了一个三轮复习计划。整个周期大约八周,总时间不算长,但节奏感很重要。我见过不少同学第一周猛刷两百题,第二周开始疲软,第三周直接弃疗,最后靠考前突击的“玄学”。这不是健康的复习方式。复试算法是持久战,我更推荐用**“基础轮—专题轮—模拟轮”**的递进结构。
2.1 基础轮:把数据结构和复杂度计算打透
第一轮不碰难题,只做两件事:刷教材基础知识和我之前写过的代码模板。数据结构部分,我最常用的是严蔚敏版的框架,再加上一些我觉得写得更贴近实践的笔记类资料。
这一轮我给自己定了几个硬性指标:
- 数组、链表、栈、队列、哈希表、树、堆、图,能手写代码实现插入、删除、查找的完整过程;
- 每个常用操作能准确说出平均复杂度、最坏复杂度、额外空间复杂度,并且能解释为什么;
- 排序算法必须能手撕冒泡、插入、选择、快排、归并、堆排,尤其是快排的递归写法和非递归写法都要能立即写出来;
- 复杂度分析必须达到“看一眼代码,就能用Master定理或代入法手算出主项”的程度。
为什么复杂度这么重要?因为复试问答里老师太爱追问复杂度了。你写一个暴力解法,老师一定会问“还能不能再优化”,这个问题的核心就是你懂不懂复杂度从哪里来、瓶颈在哪。
2.2 专题轮:按模块攻破经典算法模型
第二轮开始刷专题,每个模块花三到四天时间集中打穿。我的专题顺序是:
- 二分查找和二分答案:注意边界、死循环、整数溢出;
- 链表操作:反转、合并、环检测、相交节点、删除倒数第k个节点;
- 二叉树:三种遍历的递归与非递归、层序遍历、最近公共祖先、路径和;
- 图论:邻接表建图、DFS/BFS、拓扑排序、最短路(Dijkstra、Floyd)、并查集;
- 动态规划:线性DP、背包DP、区间DP、最长子序列、编辑距离;
- 字符串:KMP、字符串哈希;
- 其他经典:贪心、滑动窗口、双指针、模拟。
这个阶段我用的刷题平台以LeetCode为主、牛客为辅。牛客适合针对性练习面试场景,LeetCode的题目分类更清晰、答案社区更成熟。每天固定刷四到六题,遇到卡了两个小时还没思路的题,我会直接看题解,但看完之后一定会自己重新写一遍完整代码,并且把题解的思路用自己的语言讲一遍。这样才能从“哦这题我会做”变成“这题我讲得清楚为什么这么想”。
2.3 模拟轮:给自己制造真实的考场压力
到最后两周,我不再追求新题量,所有精力都用来做限时模拟。
具体做法是:每天上午固定拿出两个小时,找一套目标院校往年的机试题或者LeetCode类似的组合题,完全按照考试环境来:只开一个编辑器,不开题解、不查资料,时间一到立即停笔,然后对照标准输入输出自己判分。
这个过程的收益比想象中大得多。我发现自己在无人监控时容易犯的毛病:想复杂了、不敢暴力、纠结于一行优雅写法而浪费十分钟、边界条件只测了样例没测全。这些问题不模拟根本暴露不出来。
3. 高频考点逐个拆解:原理、手撕模板、易错点
这一节是全文最核心的部分。我不会把所有算法都长篇大论,而是挑出我在复试复习过程中实际花时间最多、出现频率也最高的几类,分享我自己的理解维度和踩过的坑。
3.1 排序算法:别以为会写就真懂了
排序是最容易让考生“轻敌”的考点。很多人冒泡、快排代码都背得滚瓜烂熟,但被问“快排为什么不稳定”“归并的额外空间复杂度到底是多少”“堆排和快排实际效率差在哪”就卡壳。复试老师对排序的追问通常都不是代码本身,而是背后这些和工程选择有关的问题。
我的复习提纲是:先抓住稳定性、空间复杂度、时间复杂度这三大属性,然后针对每种排序手写标准实现。
| 算法 | 平均时间复杂度 | 最坏时间复杂度 | 额外空间 | 稳定性 |
|---|---|---|---|---|
| 冒泡排序 | 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)的临时数组来merge,这在机试和面试里都是高频追问点。如果你能顺便说出“可以用原地归并优化空间,但实现复杂且会退化”,就已经超过很多人了。
另外,手撕代码时一定要让线段清晰,一个函数只做一件事。我复习期间就是按模板化的方式整理代码,比如快排的partition函数单独提取出来,堆排分为adjustHeap和heapSort两层。这样在考场上即使紧张,写错单步逻辑的几率也会降低。
3.2 字符串匹配:KMP的真正价值在于理解next数组
KMP算法几乎是科班复试必考的概念题。让我死记硬背的时期根本记不住,后来我换了个思路:只看next数组到底在存什么。
简单来说,KMP在失配时利用“前缀=后缀”的信息,让模式串不用退回开头重新匹配。每次失配,主串指针不动,模式串指针跳到之前匹配好的公共前后缀的下一位置。真正需要理解的是:
- next[i]表示“当模式串第i位匹配失败时,模式串跳转到哪一位继续尝试”;
- 求解next的过程本身就是一个“模式串自己匹配自己”的过程;
- 时间复杂度是O(m+n),因为两个指针各自不回头。
复试被追问的变体还有“next数组怎么求”“能不能用KMP解决字符串循环节问题”“如果求最长相等前后缀怎么办”。这些我建议都提前整理成文字笔记,面试手撕时就算写不出来完整KMP,能准确讲出next的思路和复杂度,也比支支吾吾要强很多。
3.3 搜索与图论:DFS/BFS是面试问不出死角的分水岭
搜索算法看起来简单,但复试时老师的追问深度往往很惊人。常见的追问链是:
- “给定一个迷宫,怎么求从起点到终点的最短路径?”
- “你会用BFS,那BFS需要在什么条件下才能保证第一次找到的路径最短?”
- “如果图中有权重,你怎么处理?Dijkstra和BFS的关系是什么?”
- “如果没有启发信息,你会怎么做?你会考虑A*吗?”
这一连串问题其实是层层递进的。我在复习时就把DFS、BFS和Dijkstra、A*放在同一个专题里对比,画了一张纯文字对照表:
| 算法 | 数据结构 | 适用场景 | 核心思想 |
|---|---|---|---|
| DFS | 栈/递归 | 连通性、全排列、回溯类 | 一条路走到黑,不行则后退 |
| BFS | 队列 | 无权图最短路、层次遍历 | 层层扩散,先到先得 |
| Dijkstra | 优先队列 | 非负权最短路 | 贪心+松弛 |
| A* | 优先队列+启发函数 | 带目标导向的路径搜索 | 实际代价+估计代价 |
复习到这里,我最深的体会是:别把算法当成孤立的“题”,而是当成一整套解决问题的工具链。复试老师的很多问题并没有明确说“用BFS”,但如果你的脑子里没有一个算法地图,现场临时拼凑是拼不出来的。
至于Tarjan、Floyd、匈牙利这类稍微进阶的图论算法,我是在基础题都掌握得很熟之后才额外补充的。Floyd的原理非常简洁,三重循环动态规划,写起来十几行,适合作为“我了解多源最短路”的谈资;Tarjan则用来求强连通分量和割点,性价比也高;匈牙利算法是二分图最大匹配的经典解法,思路是“增广路径”,背熟模板有好处的。
3.4 贪心算法:别掉进“看起来对”的陷阱
贪心算法在复试中出现的频率非常高,因为代码短、思路直接,特别适合做手撕题。但它也是最容易被追问“为什么贪心是对的”的算法。
经典例题比如“会议室安排,尽可能安排更多的会议”“跳跃游戏”“分发饼干”等,绝大多数人的误区是:能AC就完了,根本没想过证明。但复试老师一定会问:“你凭什么认为贪心能得到最优解?”
我的应对策略是为每个贪心题至少准备一种证明思路,常见的有三种:
- 交换论证:假设最优解和贪心解在某一步不同,交换后不劣;
- 归纳法:证明贪心选择后子问题仍为同类型问题;
- 反证法:假设贪心不优,推出矛盾。
拿区间调度来举例,按结束时间从早到晚排序,选择结束时间最早的区间,然后删掉冲突区间,如此往复。证明核心是“最早结束的区间一定存在于某个最优解中”,这是一个典型的交换论证。把这个逻辑讲清楚,比写一百遍代码都有效。
3.5 动态规划:从“会写状态转移”到“会讲为什么”
动态规划是复试拉开差距的一块。考察方式通常不是简单背模板,而是给定一个实际问题,让你现场设计DP。
我最推荐的复习方法是:把状态定义、转移方程、初始条件、遍历顺序、复杂度五个要素完整写出来,而不是只写AC代码。例如最长上升子序列这道老题,我复盘时都会把每个要素写在笔记里:
- 状态:dp[i]表示以第i个元素结尾的最长上升子序列长度;
- 转移:dp[i] = max(dp[j] + 1),其中j < i且a[j] < a[i];
- 初始:dp[i] = 1;
- 遍历顺序:从左到右;
- 复杂度:O(n^2),可以优化到O(n log n)。
复试时你要能做到“直接说结论并解释状态设计的原因”。为什么dp[i]要定义成“以i结尾”?因为我们想利用之前的子结果,而“结尾”信息对应了转移的依赖条件。这些表达上的细节,才是手撕代码时的加分项。
经典模型我整理了一个清单:0-1背包、完全背包、最长回文子串、编辑距离、最长公共子序列、打家劫舍、股票买卖(含冷冻期)、矩阵路径最小和、爬楼梯变体。每个模型都要能写状态转移并能回答“为什么能否从状态中减一维”这类问题。
4. 手撕代码的临场方法:我被现实教育过的五个细节
复习阶段说得再多,最后还是要在考场上写出来。这一节我想分享我在模拟和真实场景中被“教育”过的几个细节,希望能帮大家少踩坑。
4.1 先讲思路,再动手写代码
我知道很多人会有一个执念:看到题就立刻想写代码,觉得“边写边想”显得手快。但在复试这种高压场景里,这种习惯非常危险——写着写着发现思路铺不开,或者漏掉一个重要分支,回头改的时候整块代码都乱掉了。
老师更欣赏的方式是:先把问题复述一遍,确认输入输出的边界,然后说一句话介绍思路,再快速分析复杂度,最后开始写。这个过程不超过一分半,但能让老师全程跟上你的节奏。如果有人问“为什么用BFS而不用DFS”,你也能当场回答出“因为无权图BFS能找到最短路且层数递增”。
4.2 边界条件是手撕代码的隐形分水岭
模拟轮里我吃过最大的亏就是因为边界条件丢分。lease指针返回值、数组越界、字符串空串、图节点数为0、整数溢出、除数为0——这些都在复试题目里出现过,而且一旦踩中就是全盘崩溃。
我给自己定了一个检查清单,写完后逐项排查:
- 输入为空/长度为0/只有一个元素时是否正常;
- 循环终止条件是否会出现死循环或提前终止;
- 指针操作是否可能导致空指针或悬挂;
- 大数相加是否溢出;
- 树或链表操作中是否忘记连指针或漏更新指针;
- 输出格式是否与题目要求完全一致。
提前测完这六项,代码的“存活率”至少在模拟中提升了不少。
4.3 代码规范:别让老师在你写的代码里找自己
复试手撕代码不追求优雅到极致的缩写,追求的是清晰、规范、可读。变量名别用i、j、k满地跑,至少用n、m或left、right这样的语义化名称;函数最好能拆成带名字的小函数;注释不用写满,但关键步骤点一句也没坏处。
面试现场经常出现的情况是:某个地方卡壳了,老师会往你的代码旁边指一下说“你这行是不是有点问题”。如果你的变量名语义清晰,这时候你跟老师的交流成本很低;如果全是a、b、c、d的临时变量,老师念起来都费劲,想帮你都没法帮。
4.4 优化方向的展示:从暴力到最优解的层进式阐述
我在模拟面试中特别练过一件事:写完一个解法之后,主动补充“还有更优解法”的能力。
复试题目往往不会只要求你会一种解法,老师会追问“能不能再优化”。如果你一开始就从最优解讲起,会显得说服力不够;如果你只会暴力解,又容易显得深度不足。最好的策略是先讲暴力解和它的复杂度,再说“这里我可以通过XX优化到O(n log n)”,并同步说明优化后的空间是O(1),然后直接把优化代码写出来。
这样既展示了覆盖能力,也让老师看到你有优化的敏感度,而不是把思路藏到最后等对方问。
4.5 复盘的输出方式:把每次练习都变成一个小项目
这部分是我认为这一整轮复习里价值最高、也最容易被忽略的。我每周会花一个晚上,把本周做过的题目整理成一份笔记,格式如下:
- 题目标题和题号;
- 我的第一想法和分析过程;
- 正确解法和复杂度分析;
- 我写错或卡住的具体位置;
- 如果复试再问,我会怎么组织回答。
这套笔记积累到最后,已经变成了一本可复盘的算法手册。考前冲刺我只翻自己写过的易错点,比盲目刷题效率高得多。
5. 复试之后的延伸价值:工程算法和科研方向的额外沉淀
复试算法复习这件事,并不随着成绩公布而结束。我在准备过程中发现,热搜词里那些看似和复试无关的方向——PID控制、滑动平均滤波、粒子群算法、随机森林、YOLO、强化学习——其实是读研阶段会遇到的真实算法场景,提前留个心是很有意义的。
5.1 复试中的算法延伸题可能来自这些方向
有些学校复试面试会结合导师研究方向提问。比如你报的是智能控制方向,老师可能随口问一句“知道PID吗”;你报的是数据挖掘方向,老师可能聊到聚类、线性回归或随机森林;你报的是图像处理方向,语义分割和YOLO可能成为高频词。
我的建议是提前看目标导师最近三年的论文关键词,把和它们关联的算法名称至少做到“能说出原理一句话、能说出适用场景、能说出和基础算法的关系”,不需要像基础算法那样手撕代码,但也不能完全不沾边。
- PID算法的核心是比例、积分、微分三个环节,用误差做反馈调整;
- 滑动平均滤波是用来抑制噪声的,多用于传感器数据预处理;
- 粒子群算法属于群体智能优化,靠“个体历史最优+群体历史最优”联合更新位置;
- 随机森林是决策树的集成学习,原理是训练多棵树再投票;
- YOLO将目标检测看成回归问题,一次前向推理直接输出边界框和类别;
- BPTT是循环神经网络按时间展开的反向传播,本质上是链式法则的叠加。
这些方向如果事先做过功课,复试时哪怕只是自然带一句“我了解过”,给人的整体印象也会不一样。
5.2 从“会基础算法”到“能用工程算法”的连接
复试结束后我也在反思:考研复试考算法,本质上是筛选具备“抽象问题、设计解法、实现验证”的人。基础算法和工程算法并不割裂。比如你把PID当成一个简单的反馈闭环,把滑动平均滤波当成一个固定窗口的移动和值计算,再回头看数据结构里的队列操作,就会发现它们其实是一层一层叠上去的。
这种视角让我后来真正面对工程和科研课题时,少了很多“不知道从哪里入手”的焦虑。算法复习留下的最大财富,不是背下了多少模板,而是建立了一种“看到一个问题,先去拆解它的输入、输出和约束,再选择合适算法”的思维方式。
5.3 为什么我建议把复习笔记坚持写到复试之后
很多同学复试结束就把算法笔记扔进角落,我觉得挺可惜。因为读研阶段你会发现:看论文需要算法基础,跑实验需要改代码,甚至帮导师做横向课题的时候,可能突然要用到之前学过的一个冷门算法。如果当时把笔记完整保留下来,按主题和难度打包好,之后随时都能检索。
我自己的做法是把所有笔记按标签分类,例如“排序”、“图论”、“DP”、“字符串”、“工程算法”,每个标签下再按“原理→模板→易错点→例题”组织。复试后我又陆陆续续补充了一些读研阶段实际用到的算法,这份文档慢慢成为我自己的算法手册。
6. 最后一个建议:把复试算法当成一场思想方法训练
准备复试算法的过程,其实不只是为了打赢一场考试。它逼着你把模糊的直觉变成严格的逻辑,把背过的模板变成能在纸上重新推导出来的公式,把看到难题想放弃的冲动变成按部就班拆解的耐心。
我最大的感受是,所有算法题都像在训练一种“提问的习惯”:这个问题的输入到底是什么?边界在哪?如果数据量变大,我的解法还成立吗?有没有更优的时空权衡?这些问题不只在机试里有,在做研究、写项目、读论文时同样成立。
所以我的最终建议是:不用把复试算法当成“最后一关”,而是当成一种提前预习研究生生活的思维课。当你真的把快排的partition原理理解到可以当场推导,把动态规划的状态设计习惯练到看到陌生题也能冷静建模,复试本身就不会再是一件让人紧张的事情。
说到底,复试算法记录的不只是一堆代码,更是一种解决问题的思维方式。希望这份记录对你也有用。