ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

小米算法岗笔试复盘:KMP、动态规划与编码实战全解析

小米算法岗笔试复盘:KMP、动态规划与编码实战全解析 2021年小米秋招算法方向第一场笔试说句实话考完走出来我印象最深的不是哪道题特别难而是整体节奏比想象中快。两个小时看起来充裕实际分配不好连编程题都写不完。这篇文章我把自己复盘后的题型结构、高频考点和一些做题策略整理出来希望能给后面想投算法岗的同学一点参考。1. 第一场笔试的题型分布与做题顺序复盘1.1 整场笔试的整体结构与分值倾向小米算法岗笔试一般分两大块一块是客观题以单选多选为主覆盖数据结构、机器学习、深度学习理论另一块是编程题通常是两到三道用来自动判题。客观题看着简单实际很容易在KMP的next数组、排序算法的稳定性、卷积输出尺寸这类细节上翻车。编程题则直接决定能不能进下一轮因为算法岗投递人数多笔试筛选时编程题AC数量基本就是硬指标。我印象里客观题大概占三到四成分数剩下全是编程题。客观题里数据结构和机器学习几乎对半开偶尔会有一两道概率统计或线性代数的送分题比如给一个3x3矩阵求特征值、给两个事件求条件概率。这类题复习过就有分没复习就只能凭感觉蒙所以考前一两天把概率论和线代的核心公式过一遍是值得的。1.2 做题顺序的现实考量我当时拿到卷子先花几分钟把所有题目扫了一遍标记出自己最有把握的编程题然后直接从那道题开始做。原因很简单笔试是自动判题AC一道是一道先拿稳一道题的分比卡在一道难题上死磕更划算。客观题放在最后填因为它的分值相对分散即使时间来不及蒙几个选项损失也没那么大。这里有一个容易被忽略的点有些笔试平台提交后不能回头改代码或者题目之间存在跳转限制。我习惯在写编程题之前先确认平台的规则避免一个不小心把还没写完的代码交上去。还有编译器环境最好提前确认支持的语言版本我记得有些老平台默认是C11想用C17的某些写法会直接编译不过这种非技术因素丢分太冤。2. 基础算法与数据结构现场最容易卡壳的几类题2.1 KMP与字符串匹配的next数组推导热搜词里“在KMP算法中对于模式串pabacaba其next数组”被反复提及说明这是很多人搜过的经典考点。KMP的核心思想是主串指针不回退模式串失配时根据next数组来回退从而把匹配复杂度压到O(nm)。难点不在思路而在next数组到底怎么求、怎么定义。常见的next数组定义是next[i]表示模式串的前i个字符组成的子串中最长的相同前缀后缀长度。注意有些教材把next数组整体往后平移一位导致next[0] -1、next[1] 0代码写出来和解释略有差异。我在现场一般用下面这种写法vectorint getNext(const string p) { int n p.size(); vectorint nxt(n 1, 0); nxt[0] -1; int i 0, j -1; while (i n) { if (j -1 || p[i] p[j]) { i; j; nxt[i] j; } else { j nxt[j]; } } return nxt; }拿“abacaba”手动推一遍初始化nxt[0] -1i1时前一个字符是a最长相等前后缀长度是0所以nxt[1]0i2时前两个字符是ab0i3时前三个字符是aba前缀a和后缀a相等长度为1所以nxt[3]1继续推下去最终得到的数组是[-1, 0, 0, 1, 0, 1, 2, 3]。这个数字3对应整个模式串“abacaba”里前缀“aba”和后缀“aba”的重合长度也是KMP匹配时最后一次失配能回退到哪里的依据。我在笔试前总结过一个快速验证方法拿一个简单字符串比如“aaaa”手动算一遍next数组再对比自己背的模板代码输出是否一致。能对上说明这块基本稳了对不上大概率是边界条件或者数组移位理解错了。这种基础题一旦考到丢分非常可惜。2.2 排序算法的选择与复杂度陷阱排序算法是选择题的常客直接考时间复杂度和稳定性。我整理过一张表笔试前过一遍能省不少记忆负担排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性冒泡排序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^2)。所以很多题目会提到“随机化快排”或者“三数取中”就是为了规避这种退化。现场如果真遇到排序题别急着套模板先看数据范围再决定用什么算法比背模板靠谱得多。手写排序算法还有一个容易踩的坑边界条件。我写过很多次堆排序每次都会在“取末尾元素和堆顶交换后如何堆化”这一步停顿几秒。笔试时间紧这种停顿很容易打乱节奏。备考时我建议把快排、归并、堆排各写十遍写到条件反射这样即使考到也不会慌。2.3 贪心与动态规划的快速识别贪心和动态规划在算法岗笔试中出现频率极高。最简单的判断方法如果每一步最优能直接推出全局最优且无后效性那就是贪心如果存在重叠子问题需要记录中间状态那就是动态规划。举个例子区间调度问题按结束时间排序选最多不重叠区间是贪心而背包问题必须用DP表逐步转移因为它们面临的约束结构完全不同。笔试时间紧张我一般先尝试从递归角度想。能写出“f(i) max(f(i-1), f(i-1) value)”这种递推式基本就是DP如果想法里带着“当前最大”“当前最优”这种词往往就是贪心。还有个小技巧编程题里如果数据范围是n 1000左右很可能要求O(n^2)或O(n^2 log n)的DP如果n 10^5那多半是贪心或者O(n log n)的DP优化版本。动态规划题我习惯先写暴力递归再用一个备忘录数组优化成记忆化搜索最后改成迭代形式。这样即使面试官追问状态定义也能解释清楚。这个方法在笔试里也能用因为自动判题只关心结果对不对不会因为你的代码是记忆化搜索而不是标准DP就扣分反而更容易查错。3. 机器学习与深度学习方向概念题背后的功夫3.1 经典模型与底层逻辑的考察方式小米算法岗笔试对机器学习有一定侧重。LR、SVM、KNN、朴素贝叶斯、决策树都出现过。选择题常考的点包括LR用极大似然估计还是最小二乘法、SVM的核函数作用、KNN的k值大小对偏差方差的影响、决策树的划分指标是信息增益还是基尼指数。这些概念不深但记忆容易模糊尤其是“knn算法的应用能力包括哪三个方面”这种题其实是在考对模型机制的理解。我复习的时候习惯把经典模型当成一个个“小系统”来理解输入是什么模型参数是什么损失函数是什么如何优化如何预测。比如LR输入是特征向量模型参数是权重w和偏置b损失函数是交叉熵优化方法是梯度下降预测时输出一个概率值再卡阈值。这样记忆就不容易乱。当然这样分类的边界就清楚了。有面试官问过我“LR为什么不用MSE做损失函数”这个问题背后就是对凸优化和梯度消失的理解。如果笔试里出现类似概念能说出“交叉熵的梯度形式和sigmoid的梯度形式不同”就更稳。3.2 损失函数和评估指标的对应关系损失函数和评估指标是客观题的重灾区。我见过的考法有回归任务用MSE还是MAE、分类任务为什么用交叉熵、精确率和召回率在什么场景下要权衡、AUC和ROC怎么理解、F1如何计算。这些题分数不高但出现频率很高。这里有个容易混淆的点训练用的损失函数和评估用的指标往往不是同一个东西。比如训练一个分类模型损失函数用交叉熵但上线后关心的可能是F1或者AUC再比如不平衡数据集上准确率这种指标容易被多数类带偏需要看PR曲线。笔试选择题如果问“样本严重不平衡时哪个指标更合理”正确答案通常是F1或AUC而不是准确率。我自己备考时会特意拿一张纸把常见损失函数和评估指标的公式写一遍。写一遍比看十遍都管用。尤其是交叉熵的公式和F1的计算方式这些是笔试高频的“背多分”题。3.3 深度学习理论题卷积尺寸计算与网络结构深度学习相关的选择题主要是卷积操作、池化、反向传播和一些网络结构的基础知识。我在笔试中遇到比较多的是卷积输出尺寸计算公式很简单out (in - kernel 2 * padding) / stride 1举个例子输入是32x32的图用5x5卷积核padding0stride1输出尺寸就是(32 - 5 0) / 1 1 28。这种题基本属于送分但要注意取整方向很多题告诉你stride不能整除问结果是向下取整还是多少这种细节很容易丢分。反向传播的题则通常问梯度怎么传。比如“一个卷积层的参数有多少”直接根据输入输出通道数、卷积核大小算即可。还有关于BatchNorm原理和Dropout作用的题我当时的经验是“用大白话解释一遍”——BatchNorm让每层的输入分布稳定Dropout在训练时随机丢弃神经元防止过拟合。能说清楚这两个“为什么”选择题基本就不会错。4. 编程题实战从读题到AC的完整链路4.1 读题阶段的陷阱与快速信息提取编程题最容易翻车的地方其实在读题。很多题目为了营造场景会给你一大堆业务描述真正的约束条件藏在最后几行。我读题时习惯先把“输入范围”“时间限制”“特殊规则”标出来因为它们直接决定算法选型。一个实用的判断方法n不超过10^3O(n^2)是安全的n不超过10^5O(n log n)是安全的n到10^7甚至更大基本要求O(n)。如果题目里出现“对10^97取模”基本是在引导你用动态规划之类带累加的算法。如果题目里出现“保证答案在32位整数范围内”那可以少考虑大数溢出如果没写最好直接用long long或int64防一手溢出。4.2 复杂度估算与暴力保底策略笔试不是面试自动判题只看通过率不看你是否用了最优解法。所以我的策略永远是“先写出能跑的暴力解法再根据性能要求优化”。举个例子第一反应是二重循环能解决但数据范围要求O(n log n)那就先把二重循环写出来用随机数据验证正确性再改成排序、二分或哈希版本。这种“先保对再优化”的策略能显著降低现场压力。我整理过一个复杂度参考表笔试前可以扫一眼数据规模可接受时间复杂度常用算法方向n 1000O(n^2)双重循环DP、暴力枚举n 10^5O(n log n)排序二分、堆、线段树n 10^6O(n log n) 或 O(n)排序、KMP、线性扫n 10^9O(log n)二分答案、快速幂、数学公式如果现场实在没有思路也可以写一种“针对小范围暴力、大范围优化”的分段策略比如n小于某个阈值时用暴力大于时用数学公式。虽然不一定能AC全部用例但至少能拿部分分。4.3 边界条件与大数据量下的稳定性编程题AC率高的人往往不是算法最花哨的人而是边界条件考虑得最全的人。我每次提交前都会检查这几点输入是否可能为空、数组是否只有1个元素、是否存在负数、会不会有重复元素、求和是否会溢出、字符串是否包含大小写和空格。这听起来琐碎但真实笔试中很多案例翻车就翻在这些地方。一个有效的方法是“对拍测试”写一个暴力解作为基准再写一个优化解生成大量随机小数据把两个结果对比。如果多次随机测试结果一致那边界出错的概率就小很多。现场笔试可能没时间写完整对拍但可以用这个思路来检查比如自己脑内造几个特殊输入测试一下代码是否崩溃。一个小技巧在循环里加大括号哪怕只有一行逻辑也能避免后续调试时误加语句的坑。5. 这次笔试暴露出的备考盲区与修正5.1 我踩过的坑刷题量不等于做题能力在准备小米这场笔试之前我刷了不少题算法题数量也算可观但第一次参加这种限时笔试还是发慌。问题不在会做而在于“按规定时间做出来”和“在紧张状态下不犯错”是两回事。刷题时我习惯慢慢想卡住了就看题解结果笔试现场看到一道中等难度的题第一反应不是自己建模而是回忆“我是不是见过这道题当时题解怎么写”这种依赖感反而拖慢速度。后来我调整了刷题方式每次做题严格计时一道经典题最多给40分钟写不出就把思路写在纸上然后对照题解复盘。这种方式看起来效率低但坚持下来之后笔试现场的心态稳定了很多。还有一个心得是分类刷题不如定时刷“混合套题”——因为真实笔试压根不会告诉你这题该用DP还是贪心你得自己判断。5.2 针对算法岗笔试的高效复盘方法笔试后复盘比刷新题更重要。我现在每道错题都会做一张“信号-算法”映射表把题目里的关键信息绑定到对应算法上。比如看到“第K大”就想到堆排序或快选看到“无环单源最短路”就想到Dijkstra看到“字符串匹配”就想到KMP看到“最长公共子序列”就想到二维DP。这些映射一旦建立笔试读题速度会大幅提升。我还会把客观题里的概念错题单独记下来。比如“哪些排序算法是稳定的”“SVM核函数有哪些”“卷积输出尺寸公式”这些属于记忆型考点适合考前快速过一遍。备考资料不需要多一个笔记本加一套高频错题集就够了关键是抓住“反复错”的那些点而不是从头到尾再看一遍教材。5.3 从笔试到面试的衔接把题目变得立体笔试结束不代表这个项目就结束了。我会把所有没AC的编程题重新补完并且思考一下如果面试官问“这道题为什么用这个算法”我该怎么答。比如一道字符串匹配题如果用KMP实现我会顺便复习next数组的推导如果用哈希做滚动哈希匹配我会去查一下哈希冲突的规避方式。这样笔试中的每一道题都能变成面试题库的素材。另外我会留意一下小米的业务方向比如手机、IoT、大模型相关场景想一下算法在这些场景里的实际应用。搜索、推荐、图像分类这些方向笔试里的机器学习概念题其实都能找到对应。比如KNN分类可以用在用户画像召回聚类算法可以用在用户分群排序算法在广告推荐系统里就是精排的基础。把这些链路想清楚笔试不仅是在答题也是在为后续面试的“业务场景题”做铺垫。这场笔试给我最大的收获就是算法岗的笔试归根结底考察的是“在有限时间内把问题抽象成算法并落地实现”的能力。它不是智商测试而是熟练度测试。如果你现在也在准备秋招笔试我建议把基础数据结构、经典机器学习概念、代码模板的熟练度这三件事排到最高优先级。等到笔试那天你真正需要做的就是把平时练过的东西稳定输出出来。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进