ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

小红书算法岗笔试复盘:KMP、动态规划与高频考点全解析

小红书算法岗笔试复盘:KMP、动态规划与高频考点全解析 二月底那阵子邮箱里突然弹出小红书的笔试通知标题就是“2024春招-算法岗-第二批笔试”。当时我正刷题刷得昏天黑地说实话点开邮件的时候心跳是加速的——一方面觉得机会来了另一方面又担心第二批笔试会不会比第一批更凶险。考完之后我第一时间把题目背了出来又花了好几天复盘每道题的考点和更优解今天就把这场笔试的完整情况、题型分布、高频考点和踩坑细节整理出来给后面要参加算法岗笔试的同学做个参考。整场笔试给我的整体感觉是不像某些大厂那样只考八股或者只考刷题而是比较均衡地覆盖了数据结构与算法、机器学习基础、深度学习基础外加两三道需要真正动脑的编程题。你要是只会调包不会手撕代码或者只会刷题不懂模型原理都会比较难受。所以我这篇复盘就按考试的实际模块来展开每个模块我都会拆到具体的知识点、我当时是怎么想的、以及事后重新整理的正确解法。1. 这场笔试到底考什么题型分布与考试体验1.1 考试平台与整体时间线先说一下考试的基本安排。笔试是在牛客网这类在线笔试平台上完成的要求开摄像头有些还会要求双机位所以考试前一定要找个安静、光线正常、网络稳定的环境。我当时提前半小时调试设备把手机支架也架好了结果发现监考系统在考试中途还会随机抓拍所以别想着中途做点别的老老实实做题。整体时间一般在90到120分钟之间题型分为两大部分客观题选择题和编程题。客观题大概有20到30道编程题通常是3到4道每道题分值不低尤其是最后一道往往决定你能不能进入下一轮。值得注意的一点是不同方向比如搜推方向、视觉方向、NLP方向的题目会有一些侧重差异但公共部分基本都跑不掉数据结构、机器学习、深度学习这三块。我参加的第二批笔试选择题的覆盖面比较广编程题则整体偏“数据结构动态规划字符串处理”的组合。1.2 选择题覆盖范围选择题考得比较杂但也不是完全无迹可循。根据我的回忆和后续和其他同学的交流以下几个模块是出现频率最高的数据结构与算法栈和队列的应用、二叉树遍历、堆排序、哈希表冲突处理、KMP的next数组含义、排序算法的稳定性与时间复杂度。机器学习基础偏差与方差、过拟合与正则化、K-Means聚类、KNN、朴素贝叶斯、决策树、特征选择、交叉验证。深度学习基础激活函数、反向传播、卷积神经网络的基本结构、RNN/LSTM、注意力机制、BatchNorm的作用。数学与概率统计贝叶斯公式、极大似然估计、期望与方差、矩阵特征值、条件概率。很多同学容易忽略数学和概率统计这一块实际上小红书这类偏推荐、搜索场景的业务对概率统计的要求并不低。选择题里考个贝叶斯或者极大似然都是比较常规的操作。1.3 编程题的题量与难度节奏编程题我记得是4道难度梯度还是比较明显的。前两道相对基础基本是“给你一个场景让你写个模拟逻辑”或者“考察某一个经典算法”第三道开始上强度涉及动态规划最后一道则是比较综合的字符串数据结构题需要用KMP这类算法才能拿满分。这里要提醒一个策略先快速浏览全部编程题不要按顺序死磕。我当时先扫了一遍四道题发现最后一道KMP相关的题我比较有把握就先把它写了然后回头做前面的模拟题和动态规划题。事实证明这个决策很正确因为KMP这种题只要你背过模板写起来就是纯默写但模拟题反而容易因为细节多而卡住。2. 编程题专项复盘考点拆解与代码思路2.1 字符串题KMP的next数组到底怎么推这次笔试最后一道编程题是字符串匹配相关的标准解法就是KMP算法。热搜词里恰好有人问到“在KMP算法中对于模式串pabacaba其next数组是什么”说明KMP确实是校招笔试题里的常客。先明确next数组的定义不同教材的写法略有差异。常见的一种定义是next[i]表示模式串p[0:i]这个前缀子串中最长相等前后缀的长度。换句话说我们看p[0]到p[i]这一段它的前缀和后缀最多能有多长是相等的这个长度就是next[i]。拿p abacaba来手动推一遍。为了统一我们采用一种常用的实现方式next数组的下标从0开始next[0] -1表示不存在相等前后缀时的“哨兵”位置。然后从前往后计算i 0前缀子串是a没有真前后缀概念约定next[0] -1。i 1前缀子串是ab前缀有a后缀有b不相等所以next[1] 0。i 2前缀子串是aba前缀有a、ab后缀有ba、a最长相等前后缀是a长度为1所以next[2] 1。i 3前缀子串是abac最长相等前后缀显然没有next[3] 0。i 4前缀子串是abaca前缀a和后缀a相等next[4] 1。i 5前缀子串是abacab前缀ab和后缀ab相等长度2next[5] 2。i 6前缀子串是abacaba前缀aba和后缀aba相等长度3next[6] 3。所以这批next数组为[-1, 0, 1, 0, 1, 2, 3]。如果你用的定义是next[i]表示失配时跳转的位置那数值上有些教材会整体偏移一位但核心思想一样。KMP的匹配过程简单说就是主串指针不回溯模式串指针通过next数组来回退。笔试中如果只考选择题能推导出next数组就够了如果考编程题需要能完整写出来def get_next(p): n len(p) nxt [-1] * n j -1 for i in range(1, n): while j 0 and p[i] ! p[j 1]: j nxt[j] if p[i] p[j 1]: j 1 nxt[i] j return nxt def kmp_search(s, p): nxt get_next(p) j -1 for i in range(len(s)): while j 0 and s[i] ! p[j 1]: j nxt[j] if s[i] p[j 1]: j 1 if j len(p) - 1: return i - len(p) 1 return -1这段代码用的是“next表示最长相同前后缀长度减一”的常见写法笔试时建议用自己最熟练的一套模板不要临时改免得边界条件出错。2.2 动态规划题状态设计是核心这次笔试有一道动态规划题难度中等偏上。原题我记得不太确切了但本质上是一个“数组上做选择求最值”的模型类似于打家劫舍或者最长上升子序列的变体。动态规划题最关键的就是状态定义。我一般会从三个问题入手题目问的是什么每一步有几种选择选择之间有没有依赖关系如果题目求的是最大值/最小值那大概率是DP如果是求方案总数也大概率是DP如果题目要求你从暴力递归开始优化那就更需要DP思维。以“打家劫舍”为例你是一个小偷沿街有一排房子每间房子里有现金但不能偷相邻的两间问最多能偷多少。状态定义dp[i]表示偷到第i间房子时能获得的最大金额。那么对于第i间房只有两种情况偷或者不偷。如果偷那么第i-1间不能偷总金额是dp[i-2] nums[i]如果不偷那金额就是dp[i-1]。所以状态转移方程是dp[i] max(dp[i-1], dp[i-2] nums[i])边界条件是dp[0] nums[0]dp[1] max(nums[0], nums[1])。这类题的坑在于很多同学能写出转移方程但是边界条件容易错。尤其当数组长度只有1或2的时候如果没做特判直接访问dp[i-1]就会越界。笔试时一定要把这类边界条件提前想好别等报错再改。另外一个容易忽略的是初始化数组的写法。Python里写[[0] * n] * m得到的每一行其实是同一个列表的引用修改一行会同步修改所有行。正确写法是[[0] * n for _ in range(m)]。笔试现场因为这种小失误丢分太不值了。2.3 排序与贪心的组合考察有一道编程题考的是排序贪心这也是算法岗笔试里的常客。常见模型包括会议室安排最多能安排多少场会议、合并区间、最少弓箭引爆气球、任务调度等等。这类题的核心套路往往是先排序再贪心。排序的依据通常是一个“结束时间”或“右端点”。拿“合并区间”来说给定一堆区间[start, end]把有重叠的区间合并。思路是先按start排序然后遍历区间维护一个当前合并区间的右端点cur_end。如果当前区间的start cur_end说明有重叠更新cur_end max(cur_end, end)否则就把当前合并区间加入结果然后开启新的区间。def merge(intervals): if not intervals: return [] intervals.sort(keylambda x: x[0]) res [] cur_start, cur_end intervals[0] for start, end in intervals[1:]: if start cur_end: cur_end max(cur_end, end) else: res.append([cur_start, cur_end]) cur_start, cur_end start, end res.append([cur_start, cur_end]) return res这个题我在笔试里没花太多时间因为思路比较固定。但我还是想提醒一句贪心算法一定要能证明为什么这样贪是对的。面试官不一定只考你会不会写代码会追问“为什么按右端点排序就是最优的”。笔试虽然不追问但面试会有。另外排序算法本身的复杂度、稳定性这类八股也要滚瓜烂熟。比如快排平均时间复杂度O(n log n)最坏O(n^2)归并排序稳定时间复杂度一直是O(n log n)堆排序时间复杂度O(n log n)但不稳定。选择题很喜欢在这些细节上做文章。2.4 图论小题拓扑排序与最短路选择题里有几道图论相关的问题编程题虽然没有单独出一道图论大题但选择题的分数也不能丢。拓扑排序考的是“有向无环图”的节点排序核心是Kahn算法统计每个节点的入度先将入度为0的节点入队然后依次出队减少后继节点的入度如果某个节点入度变为0就加入队列。最终如果出队节点数不等于总节点数说明图里有环。from collections import deque def topo_sort(n, edges): indeg [0] * n graph [[] for _ in range(n)] for u, v in edges: graph[u].append(v) indeg[v] 1 q deque([i for i in range(n) if indeg[i] 0]) res [] while q: u q.popleft() res.append(u) for v in graph[u]: indeg[v] - 1 if indeg[v] 0: q.append(v) return res if len(res) n else []Dijkstra最短路算法也是高频考点尤其是堆优化的版本。它的核心思想是每次从未确定的节点中选一个距离最小的然后用它去松弛相邻节点。因为每次取最小值所以用优先队列最小堆来实现。这两块内容建议考前把模板背熟尤其是Kahn算法和堆优化Dijkstra手写速度要够快。考试的时候不要现场推时间真的不够。3. 机器学习与深度学习选择题这些知识点必须背熟3.1 经典算法K-Means、KNN、决策树、SVM选择题里机器学习部分的比例不低而且很多都是基础但容易混淆的知识点。我挑几个高频的细说一下。K-Means聚类。考点主要集中在K值怎么确定肘部法则、初始中心点怎么选K-Means、算法是否一定能收敛到全局最优不一定可能陷入局部最优、K-Means对异常值是否敏感敏感因为用的是均值。这里有一个很容易踩的坑K-Means属于无监督学习但如果题目里告诉你“样本已经打了标签”那就不该选K-Means而应该考虑分类算法。KNN。核心思想是“物以类聚”一个样本的类别由它最近的K个邻居投票决定。考点包括K值太小容易过拟合K值太大容易欠拟合距离度量常用欧氏距离、曼哈顿距离特征需要归一化否则量纲大的特征会主导距离计算。有一个经典题目是“KNN的训练复杂度是多少”答案是训练阶段几乎为零因为它是懒惰学习真正的计算发生在预测时。决策树。考点集中在特征选择指标ID3用信息增益C4.5用信息增益率CART用基尼指数。这里要理解信息熵的公式H -sum(p_i * log(p_i))信息增益就是用分裂前的熵减去分裂后的加权熵。选择题不会让你手算太复杂的数值但简单的二分类熵计算要会。SVM。考点包括支持向量是离超平面最近的那些样本点核函数的目的是把低维不可分的数据映射到高维可分的空间软间隔引入了惩罚参数CC越大越不容忍误分类越容易过拟合。3.2 深度学习激活函数、归一化、注意力机制深度学习部分的选择题主要围绕基础概念展开。激活函数。Sigmoid输出范围在0到1之间但有梯度饱和问题Tanh输出范围在-1到1之间ReLU计算简单、能缓解梯度消失但神经元输出恒为负时会出现“死区”LeakyReLU是对ReLU死区问题的改进。考得最多的是“为什么ReLU比Sigmoid更常用”答案要答到梯度消失和计算效率两个点上。BatchNorm与LayerNorm。BatchNorm对每个特征维度在batch方向上做归一化适合CV任务但受batch size影响较大LayerNorm对每个样本的所有特征做归一化适合NLP/Transformer场景。选择题很喜欢让你判断某个场景该用哪种归一化。注意力机制。这是深度学习选择题里的“新贵”基本绕不开。核心公式是Attention(Q, K, V) softmax(QK^T / sqrt(d_k)) V。为什么要除以sqrt(d_k)因为当维度很大时点积结果的方差会变大导致softmax进入饱和区梯度极小。这个细节经常考得记牢。3.3 概率统计与数学基础概率统计的选择题核心考点集中在贝叶斯公式、极大似然估计、期望方差、正态分布、矩阵特征值。贝叶斯公式的题目往往是“已知某种病在人群中的发病率是1%检测准确率是99%问检测阳性的人真正患病的概率是多少”。这类题看着简单但考场上一紧张就容易算错。我建议把公式写出来再代入数值不要心算。矩阵特征值的考点一般是“特征值和特征向量的定义”“对称矩阵的特征值一定是实数”“特征值和迹、行列式的关系”等。这些如果是数学基础好的同学基本送分但文科转码的同学可能就要多花时间补一下了。4. 实操踩坑记录笔试现场最容易翻车的几个细节4.1 输入输出格式的坑在线笔试最容易翻车的就是输入输出格式。牛客网这类平台不给你写完整的文件读写而是需要自己处理input()而且有时候是多组测试数据每组的格式还不一样。第一个坑是多组输入。很多题目会写“输入包含多组测试用例每组占一行”这时候需要用while True搭配try...except来实现循环读取import sys while True: try: line sys.stdin.readline().strip() if not line: break # 处理逻辑 except: break第二个坑是一行里混着空格和逗号。有的题目输入是1, 2, 3有的是1 2 3还有的是[1,2,3]。保险做法是用正则或者先替换再splitdata sys.stdin.readline().strip().replace(,, ).split()第三个坑是输出格式要求。有的题要求输出结果之间用空格分隔有的要求用换行分隔还有的要求末尾不能有多余空格。这些细节错了会被判定格式错误虽说不至于0分但很影响心情。4.2 复杂度和内存的坑编程题如果数据范围给到10^5甚至10^6那O(n^2)的算法基本就是超时预定。笔试平台一般会给你提示“时间限制1秒”如果你写了双层循环很可能只能过部分测试用例。我考的时候有一道题一开始写了暴力解法结果数据范围大一点的测试用例直接超时最后换成了O(n log n)的贪心才过。所以看到题目先估算复杂度数据范围n10^5那你的算法最多只能到O(n log n)n10^3可以考虑O(n^2)n10^8基本就要想数学公式或者O(n)扫一遍了。还有递归深度的问题。Python默认递归深度是1000层如果你写了DFS递归且数据比较大就会直接RecursionError。这时候要改成迭代栈或者使用sys.setrecursionlimit(1000000)提前设置。但笔试平台有时候会禁用过高限制所以能迭代就迭代。4.3 环境与工具的坑笔试平台的Python版本可能和你本地不一样比如本地是3.10平台是3.8有些语法可能不兼容。我一般写代码时会刻意避免使用太新的语法特性比如match-case、|类型合并这些。另一个很实际的问题是不能本地调试。在线编程题的调试手段很有限只能靠print打印中间结果。所以我在笔试时有个习惯每道题只写一个核心函数然后把输入输出放到if __name__ __main__里这样既可以本地快速测试提交时也不用大改。还有一个建议是提前备好自己的代码模板。虽然笔试平台不能提前粘贴代码但你可以把常用的模板背下来比如快速排序、二分查找、并查集、KMP、Dijkstra、拓扑排序、并查集。有了这些模板遇到类似的题目就能快速改出来而不是从零开始推。5. 笔试结束之后复盘与面试衔接5.1 复盘怎么做才有效笔试结束当天人的记忆最清晰所以最好当天就花时间把能回忆起来的题目整理成文档。不用写得很正式但要把题目大概意思、你当时的解法、有没有AC、卡在了哪里记下来。第二天再重新做一遍重点看有没有更好的解法。我当时把4道编程题全部重新做了一遍发现有一道题其实可以用更简单的解法但考场上我想复杂了。这种复盘特别有价值因为它能帮你看到自己在压力下的思维偏差。另外选择题里不确定的题也值得整理。比如我当时对一道关于LSTM门控的题比较模糊考完查了资料才发现自己把遗忘门和输入门记混了。这种知识点漏洞只有在复盘时才能暴露出来。5.2 后续流程怎么衔接笔试通过后一般1到2周内会收到面试邀约。小红书算法岗的面试通常会先做一个简短的自我介绍然后深挖项目经历最后会有1到2道手撕算法题。所以笔试结束后不要觉得就完事了要提前把简历上的项目重新梳理一遍尤其要准备好“你在项目里遇到的最大挑战是什么”“特征工程你是怎么做的”“为什么选择这个模型而不是那个模型”这类问题。手撕算法题的准备方式和笔试类似但更侧重思路的清晰表达。面试官看重的是你能不能边写边讲能不能主动分析复杂度和边界条件。所以笔试结束后我会建议把常见的高频题再过一遍尤其是二叉树的遍历、DP、字符串匹配、图的最短路这几类。我个人的体会是算法岗的笔试其实是一个筛选“基本功是否扎实”的环节它考的往往不是特别偏难怪的题目而是你能不能稳定、规范、快速地写出代码。很多人平时刷题在IDE里慢慢调一到笔试就各种小错误频出归根结底是缺乏限时训练。所以无论你现在距离笔试还有多久都建议每周至少做两次模拟笔试用真实平台、限时90分钟、开着摄像头那种状态去练到了真正考试的时候才不会手忙脚乱。
返回列表