
说实话看到小红书2020校招算法笔试题卷一这个标题时我第一反应是这套题能不能完整还原已经不重要了重要的是它划出的考点边界。算法岗校招笔试这些年翻来覆去核心考点其实就那些但每家公司出题风格不一样小红书这套卷一恰好能代表互联网内容平台型公司对算法工程师的筛选逻辑——既要你懂经典的计算机基础算法又要你对机器学习、深度学习这些实际业务常用的算法有足够理解还需要你在有限时间内写出能跑的代码。如果你正准备校招算法岗或者想检验一下自己的算法功底这篇文章值得你认真看下去。我会把这份卷子涉及的考点逐项拆开补全完整的解题思路和代码实现再附上我这些年刷题、笔试、面人总结出来的实操经验。1. 试卷结构与考点布局一套校招算法题到底在筛选什么1.1 题型分布与分值逻辑2020年那会儿的算法岗笔试基本延续了选择题编程题简答题三件套的框架小红书这套卷一也不例外。选择题大概占30到40分覆盖数据结构、排序、字符串匹配、图论基础这些计算机核心知识编程题占40到50分通常是两道到三道算法实现题简答题占20分左右侧重机器学习、深度学习或优化算法的基础原理。很多人拿到试卷习惯从第一题做到最后一题这是个大坑。 我后来复盘发现这套卷子的分值密度并不均匀——选择题虽然数量多但每道题分值低而且有些属于秒杀题有些需要仔细推导。编程题则是拉开差距的关键一道完整AC的编程题往往抵得上五六道选择题。正确的策略应该是先快速扫一遍全卷把编程题的时间预留出来选择题控制在40分钟内简答题控制在20分钟内剩下的时间全部投入编程题。1.2 难度阶梯与淘汰线根据我对这份卷子的记忆和同类试卷的对比它的难度设计是典型的三段跳。第一段是送分题比如冒泡排序在最坏情况下的时间复杂度、“快速排序的平均时间复杂度是多少”这类题只要基础扎实就能秒答。第二段是中等题开始涉及KMP算法的next数组计算、堆排序的建堆与调整过程、二分图匹配的判定条件等需要动笔推导。第三段才是压轴题通常是综合性的算法设计题会结合贪心、动态规划或者图论的最短路径问题考察你能否在约束条件下选择正确的算法。淘汰线通常划在中等题和压轴题之间。也就是说选择题正确率在70%以上、编程题AC一道、简答题踩中要点基本就能进面试。但如果选择题错太多或者编程题一道都没跑通那大概率会被刷掉。这里有个很多人不知道的细节笔试阅卷时编程题并不是只看结果有些公司会人工看代码风格和思路即使没有完全AC如果思路清晰、代码结构好也有机会拿到部分分数。所以哪怕做不出来也一定要把可运行的半成品代码提交上去把注释写清楚别直接留白。2. 排序与查找的经典考法从冒泡到堆排序的递进逻辑2.1 排序算法全家桶复杂度对比与代码模板排序算法是笔试题里最基础的送分题但也是最容易失分的地方因为你以为你懂其实很多细节没吃透。这套卷子里出现了典型的复杂度对比题选项里把冒泡排序、快速排序、归并排序和堆排序混在一起问哪些是稳定排序、哪些是原地排序、最好情况和最坏情况的复杂度分别是什么。我整理了一个表格建议你直接存下来算法平均时间复杂度最坏时间复杂度空间复杂度稳定性原地排序冒泡排序O(n²)O(n²)O(1)稳定是快速排序O(nlogn)O(n²)O(logn)不稳定是归并排序O(nlogn)O(nlogn)O(n)稳定否堆排序O(nlogn)O(nlogn)O(1)不稳定是插入排序O(n²)O(n²)O(1)稳定是这里容易踩的坑有两个。第一个是快速排序的最坏情况当序列已经接近有序且每次选的基准值都是最小或最大元素时快排会退化成O(n²)。第二个是归并排序的空间复杂度很多人误记成O(1)实际上归并过程需要额外的临时数组合并空间复杂度是O(n)。2.2 堆排序的手写实现与建堆推导这套卷子涉及堆排序的考察因为它既能考数据结构又能考代码实现。堆排序的核心是两个操作建堆和调整。建堆的时间复杂度不是O(nlogn)而是O(n)很多人在这里栽跟头。推导过程不复杂从最后一个非叶子节点开始向上调整每个节点调整的代价和它所在层的高度成正比整体求和之后是一个等比数列收敛到O(n)。手写堆排序的时候核心是siftDown调整函数。我给出一个可以直接抄的C实现void siftDown(vectorint arr, int i, int n) { int smallest i; int left 2 * i 1; int right 2 * i 2; if (left n arr[left] arr[smallest]) smallest left; if (right n arr[right] arr[smallest]) smallest right; if (smallest ! i) { swap(arr[i], arr[smallest]); siftDown(arr, smallest, n); } } void heapSort(vectorint arr) { int n arr.size(); // 建堆从最后一个非叶子节点开始 for (int i n / 2 - 1; i 0; i--) { siftDown(arr, i, n); } // 依次取出堆顶 for (int i n - 1; i 0; i--) { swap(arr[0], arr[i]); siftDown(arr, 0, i); } }这里用的是小顶堆排出来是升序。如果你要排降序就用大顶堆。我在这方面踩过坑——面试官让我写堆排序我写成了优先队列的调用结果被追问底层实现一下子露馅。所以优先队列可以用但你必须能徒手写siftDown。2.3 二分查找的边界处理一个容易当场翻车的细节二分查找在笔试题里很少单独出一道题但会作为编程题的基础工具出现。这套卷子的选择题里有一道是在一个有序数组中查找第一个大于等于target的位置四行答案选项分别对应不同的边界处理方式。二分查找的边界处理是经典的翻车点。我推荐你统一使用左闭右开区间也就是[l, r)的写法这样配合标准库的风格不容易出错。查找第一个大于等于target的位置可以这样写int lower_bound(vectorint nums, int target) { int l 0, r nums.size(); while (l r) { int mid l (r - l) / 2; if (nums[mid] target) { r mid; } else { l mid 1; } } return l; // l就是第一个不小于target的位置 }注意这里的mid计算用了l (r - l) / 2而不是(l r) / 2是为了防止整数溢出。这种细节在笔试选择题里也可能直接出问你为什么写成这样。你至少要能答出来当l和r都接近INT_MAX时直接相加会溢出。3. 字符串专题KMP的next数组如何手算与编码3.1 为什么字符串匹配算法是笔试常客字符串匹配是内容平台公司的核心场景从文本检索到关键词过滤都绕不开它。所以小红书这类公司在校招笔试里一定会考KMP或者至少考到字符串匹配的优化思路。这份卷子考察了KMP算法中next数组的计算而且给出的模式串是abacaba。KMP的核心思想是当模式串的某个字符与主串不匹配时已经匹配成功的部分里存在最长相等前后缀利用这个信息把模式串尽可能多地右移避免从模式串头部重新匹配。 很多人学KMP卡在next数组的定义上因为不同教材的定义有差异越学越乱。我建议你掌握两种定义并且能在考场上明确区分。3.2 手算abacaba的next数组第一种定义是PMT表部分匹配表next[i]表示pattern[0..i]这个子串中最长相等前后缀的长度。注意前后缀不能取整个子串本身。我们以模式串abacaba为例逐个计算i0子串a没有真前后缀next[0]0i1子串ab前缀a、后缀b不相等next[1]0i2子串aba前缀a、后缀a相等且长度为1前缀ab、后缀ba不相等所以next[2]1i3子串abac没有相等的前后缀next[3]0i4子串abaca前缀a、后缀a相等长度为1next[4]1i5子串abacab前缀ab、后缀ab相等长度为2next[5]2i6子串abacaba前缀aba、后缀aba相等长度为3next[6]3所以PMT表为[0, 0, 1, 0, 1, 2, 3]。第二种定义是失配跳转数组通常让next[0]-1next[i]表示当第i位失配时模式串指针应该跳转到的位置。在这个定义下abacaba的next数组为[-1, 0, 0, 1, 0, 1, 2]。同样取最长相等前后缀的长度但要把长度转换成跳转位置也就是pattern[0..i-1]的最长相等前后缀长度。我在笔试时建议手算第一张表因为它更直观。如果题目问的是代码实现里的next数组那就要看它用哪种定义代码里初值怎么设就按哪种来。3.3 从next数组到匹配过程一份可运行的KMP模板手算next数组只是第一步很多笔试题还会让你写出KMP匹配的过程甚至直接让你补全代码。下面这份Python实现我用的是PMT表风格但数组做了偏移处理和网上主流模板兼容def get_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): nxt get_next(p) ans [] j 0 for i in range(len(s)): while j 0 and s[i] ! p[j]: j nxt[j - 1] if s[i] p[j]: j 1 if j len(p): ans.append(i - j 1) j nxt[j - 1] return ans这个模板里的j始终表示当前已匹配的模式串长度在失配时通过nxt[j-1]回退。注意最后一行的j nxt[j-1]意思是匹配成功后为了继续找下一个匹配位置把j回退到最长相等前后缀的位置不重新从0开始。这是KMP效率高的关键也是在选择题里容易考到的点。3.4 与字符串相关的其他高频考点除了KMP推荐系统、搜索引擎场景还常用到BM算法和BM25算法。BM算法利用坏字符规则和好后缀规则在实际匹配中往往比KMP更快。BM25则不是匹配算法而是信息检索领域的相关性评分函数在搜索排序和问答系统里经常出现。这套卷子的简答题里有一道涉及如何在一篇长文里快速找出所有出现过的关键词考察的就是多模式匹配的场景。单模式用KMP多模式就得用AC自动机。AC自动机可以理解为KMP在字典树上的扩展核心是fail指针的构建。如果你时间充裕建议把AC自动机也掌握了在笔试和面试中都是加分项。4. 图论与最优化Dijkstra、二分图与贪心的实战取舍4.1 最短路径问题的典型变体图论算法在算法岗笔试中的出现频率很高因为它和推荐系统中的用户-物品关系、社交通讯中的好友关系链建模直接相关。这套卷子的编程题里有一道是给定一个带权无向图求从节点0到节点n-1的最短路径长度标准的Dijkstra解法。我建议你熟练掌握Dijkstra的优先队列优化版本这是笔试中出现频率最高的图论代码。Python实现如下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易错点有两个。第一个是图可能不连通要处理好dist值为inf的情况第二个是边权可能为负一旦有负权边Dijkstra就不适用了需要用Bellman-Ford或SPFA。笔试题里通常不会直接给你负权边但选择题可能换个说法来挖坑比如以下哪种算法能处理负权边。4.2 二分图与HK算法从判定到最大匹配这套卷子涉及二分图的考点不过是以选择题形式出现的问一个给定的图是否为二分图以及二分图最大匹配的常用算法。二分图的定义是顶点可以分成两个互不相交的集合使得所有边的两个端点分别属于两个集合。判断二分图的常用方法是染色法用BFS或DFS给每个顶点染色如果相邻顶点颜色相同则不是二分图。最大匹配问题里匈牙利算法是基础HK算法是优化版本时间复杂度从O(VE)降到O(E√V)。HK算法的核心是先用BFS构建从所有未匹配左顶点开始的最短增广路分层图再用DFS分层图上寻找增广路。代码量较大笔试中手写HK的难度偏高但选择题里会考它的时间复杂度。建议你至少把匈牙利算法手写熟把HK的复杂度记牢。4.3 贪心算法的经典案例与正确性证明思路贪心算法在笔试题里属于看似简单、其实容易翻车的类型。这套卷子里有一道任务调度问题有n个任务每个任务有截止时间和收益求最大收益。很多人第一反应是动态规划其实这道题可以用贪心加优先队列求解按截止时间排序扫描每个任务当前时间超过截止时间时弹出收益最小的任务。贪心算法的关键不是写代码而是证明贪心策略的正确性。常用的证明方法有交换论证法和拟阵理论。笔试题一般不要求严格证明但简答题里会让你说明为什么这样贪心是对的。我建议你至少掌握交换相邻元素不影响最优性这个思路它能覆盖大部分贪心题。4.4 动态规划的状态设计与边界初始化和贪心相对的是动态规划。这套卷子的压轴编程题是一道二维DP给定一个网格每个格子有非负数值从左上角走到右下角只能向下或向右走求路径和的最小值。这是最经典的DP题状态转移非常简单def minPathSum(grid): m, n len(grid), len(grid[0]) dp [[0] * n for _ in range(m)] dp[0][0] grid[0][0] for i in range(1, m): dp[i][0] dp[i-1][0] grid[i][0] for j in range(1, n): dp[0][j] dp[0][j-1] grid[0][j] for i in range(1, m): for j in range(1, n): dp[i][j] min(dp[i-1][j], dp[i][j-1]) grid[i][j] return dp[m-1][n-1]这道题的难点不在转移方程而在空间优化和时间复杂度优化。你可以把二维dp优化成一维数组因为当前行只依赖上一行的值。这种优化在笔试里很可能被追问如果你能写出一维版本会是一个重要的加分项。5. 机器学习与智能优化算法在笔试中的出题形态5.1 经典机器学习算法的基础概念辨析小红书这类内容推荐平台算法岗笔试的简答题和选择题里必然会涉及机器学习算法。这套卷一的选择题覆盖了KNN、聚类、决策树这几个基础算法。KNNK近邻考的是KNN算法的应用能力包括哪三个方面——分类、回归、异常检测。KNN本身不需要训练过程它是基于实例的学习预测时计算样本与所有训练样本的距离取最近的K个做投票或平均。这里的坑点是特征缩放当特征量纲差异大时距离会被量纲大的特征主导所以必须先做标准化。这道题在选择题里就是以哪些预处理步骤是KNN必需的来考察的。聚类算法里最常考的是K-Means。它的目标函数是最小化每个样本到其所属簇中心的距离平方和。K-Means有两个经典考点一是K值怎么选常用肘部法则和轮廓系数二是初始聚类中心的选择随机初始化可能陷入局部最优所以有了K-Means。这个卷子在简答题里问过K-Means比随机初始化好在哪里核心答案是它让初始簇中心尽可能分散减少陷入局部最优的概率。决策树算法考的是特征选择标准ID3用信息增益C4.5用信息增益率CART用基尼指数。信息增益偏向选择取值较多的特征信息增益率可以校正这个偏差CART的基尼指数计算更简单不用算对数。这些区别在选择题里极其常见务必记牢。5.2 深度学习与控制论算法的交叉考点这套卷子的简答题里有一道问题PID算法在CRPS PSU Power中的作用是什么。PID是比例-积分-微分控制算法本质上是通过当前误差、历史累积误差和误差变化趋势来调整输出。P项对应快速响应I项消除稳态误差D项抑制超调。CRPS和PSU这两个词属于硬件和服务器电源管理领域说明这套题结合了实际业务场景。考到这类题我建议你的答题框架是先解释PID的基本原理和三个系数的物理意义再说明它在电源功率控制中调节电压或电流的过程最后提一下参数整定方法。即使你对硬件不熟把PID核心思想和调参逻辑写清楚也能拿到大部分分。此外这套卷子还涉及粒子群算法和模拟退火算法的选择题问的是它们各自属于哪种优化算法、核心思想是什么。粒子群算法PSO受鸟群觅食行为启发每个粒子根据自身历史最优位置和群体历史最优位置更新速度属于群体智能算法。模拟退火算法模拟金属冷却退火过程以一定概率接受更差的解从而跳出局部最优。这两个都属于元启发式算法适合解决传统梯度方法难以处理的非凸优化问题。笔试题常把两者的核心公式拿出来对比PSO是速度更新公式模拟退火是Metropolis接受准则。多目标优化问题也可以考虑NSGA-II不过这套卷子里没有出现你可以作为扩展了解。5.3 深度学习基础从反向传播到损失函数深度学习基础在这套卷子里以选择题形式考察涉及的内容包括反向传播算法BP的核心思想、常见损失函数的适用场景、以及防止过拟合的手段。反向传播就是链式法则的反复应用从输出层开始逐层计算梯度并回传。损失函数的选择要看任务类型回归任务用均方误差MSE二分类用二元交叉熵多分类用交叉熵。防止过拟合的常见方法包括L1/L2正则化、Dropout、早停Early Stopping和数据增强。这里有个高频考点需要注意L1正则化和L2正则化的区别。L1正则会使得部分权重变为0具有特征选择作用而L2正则化只会让权重变小不会等于0。选择题经常给一组图问你哪个是L1哪个是L2判别的关键就是看权重分布里有没有精确的0。6. 备考路线与临场策略我的复盘与建议6.1 按考点权重分配复习时间把这份卷子整体复盘一遍你会发现考点权重其实有规律可循数据结构与基础算法大约占30%字符串与图论占20%动态规划与贪心占20%机器学习与深度学习占20%其他杂项占10%。我的建议是分三个阶段准备。第一个阶段刷LeetCode热题重点掌握数据结构基础、排序、二分、双指针、链表和二叉树。第二个阶段刷专项分类把动态规划、贪心、图论、字符串匹配这四大类各刷20到30道题。第三个阶段做模拟笔试严格控制时间适应120分钟做3道编程题20道选择题的节奏。很多人栽在时间分配上就是因为平时刷题没有时间压力到考场上才手忙脚乱。6.2 代码风格的隐藏加分项笔试阅卷可能有人工查看代码的环节所以代码风格本身就是分数。我总结了几条实用的代码规范建议你在刷题阶段就养成习惯变量命名使用有意义的英文单词不要写一堆a、b、c复杂逻辑段写注释哪怕只是一句// 贪心选择优先安排结束时间早的任务不要写死循环和深递归注意边界条件。另外一个很重要的细节笔试环境里没有IDE的自动纠错功能语法错误全靠自己检查。我建议你平时练习时用文本编辑器写代码不要依赖IDE的自动补全这样到考场上不会因为语法问题反复编译失败浪费时间。6.3 做题顺序与时间盒子策略我总结了一套时间盒子策略亲测有效。拿到卷子后先花3分钟浏览全卷标记出送分题、中等题和难题。然后按照选择题送分题→编程题第一道→选择题中等题→编程题第二道→简答题→编程题压轴题的顺序做题。每个部分设定最大时间预算比如第一道编程题最多40分钟如果40分钟还没写出来果断放弃转到下一题。这套策略看起来简单但执行起来很考验心态。很多人在压轴题上死磕了1个小时结果前面的简单题和中等题都没时间做非常可惜。你要明白笔试是及格线游戏不是满分游戏。把中低难度题的分拿全就已经超过大部分人了。6.4 面试衔接笔试之后你要准备什么笔试通过只是第一步面试官通常会根据笔试答题情况追问。如果你在笔试卷里写了一道用Dijkstra的题面试官很可能会问如果边权为负怎么处理或者如果要求从源点到所有点的最短路径怎么做。如果你在机器学习简答题里写了K-Means面试官可能追问K-Means的缺点有哪些、怎么选择K值、如何判断收敛。所以笔试结束不等于复习结束把卷子里每道题涉及的知识点延伸看一遍能让你在面试中游刃有余。我个人的体会是校招笔试更像是排除法——先把基本功不扎实的人筛掉真正的高手是在笔试、面试、项目经历的综合考察中脱颖而出的。所以不要因为一套卷子的得失影响心态把每一套真题当作查漏补缺的机会刷完认真复盘你会发现自己一次比一次扎实。如果你正在准备算法岗校招希望这篇拆解能帮你少走一些弯路。