ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

小米算法岗笔试复盘:从KMP到机器学习的考点全解析

小米算法岗笔试复盘:从KMP到机器学习的考点全解析 去年这个时候我正坐在电脑前参加小米的秋招算法笔试。卷2的题量不算大但覆盖面相当广从经典数据结构到机器学习基础都有涉及。当时作为一名主攻Java后端、算法基础靠刷题临时抱佛脚的候选人这套卷子给我的感觉是常规题里藏着细节陷阱基础题也会突然拔高一点考你对原理的理解。现在回过头看这场笔试其实是对算法综合能力的一次很好检验。所以这篇东西不是官方题解而是我结合卷面考点、答题思路和事后复盘整理的一份拆解希望能给后面准备大厂算法岗笔试的同学一些参考。我拿到卷子的第一反应是扫了一遍题目分布心里大概估算了一下时间和分值占比。整套卷2大致分成三块数据结构与基础算法选择/填空题、两道编程题、一道机器学习原理简答题。第三块对纯刷题型选手来说可能有点意外但对算法岗而言其实很正常——它考察的不是你能不能默写API而是你对自己用到过的算法是不是真的理解了。1. 这套卷子的整体面貌题型结构与考察意图1.1 从卷面分区看算法岗的能力画像先聊聊卷面为什么这么设计。做笔试不能只埋头做题要学会反推公司想考察什么。小米的算法岗笔试从卷2来看重心落在三个方面数据结构和基本算法的扎实度。像KMP的next数组推导、堆排序的手写、排序算法的稳定性比较这些题目不是考你能不能调库而是考你在没有IDE提示、不能试运行的环境下是否还能凭积累写出正确结果。代码实现能力和优化意识。两道编程题覆盖了贪心优先队列、DFS剪枝这两类高频题型这也和算法岗日常解决搜索、调度、资源分配类问题的模型是吻合的。对机器学习基础概念的深度理解。这里不是让你说某个模型的API怎么调而是问你损失函数的设计动机、特征空间里的距离计算方式背后的几何含义。这和算法工程师在业务中做模型选型和调优时需要的思维方式是对应的。我自己的感受是这套卷子不是纯粹的LeetCode刷题卷它更像“基础算法底子 工程思维 数学模型常识”的混合体。如果你只刷题不关心原理可能在简答题和部分选择题上会卡壳。1.2 为什么这些考点成了高频标签从我的回忆来看卷2里反复出现的考点标签集中在字符串匹配、排序、贪心、图论遍历、搜索剪枝、状态转移、聚类、损失函数。这些词看似零散实际上都围绕着一个共同能力——把现实问题抽象成数据结构或数学模型再选择合适的算法求解。比如字符串匹配出现KMP不是为了让考生背代码而是考察对next数组本质的理解排序那一栏里堆排序出现多次是因为堆这个结构在TopK、定时任务、优先队列场景里非常常用。能够把这些考点和实际场景对应上才算真正掌握了笔试想要的东西。还有一个容易被忽略的细节卷子里关于“算法复杂度分析”的考察点不是以单独大题出现而是藏在小问里。比如某道编程题要求优化到O(n log n)如果你只写出了O(n^2)的解法可能只能拿到一半分。这说明笔试的目标不光是做对也看你有没有性能意识。2. 数据结构与基础算法选择题里的送分题与陷阱题2.1 KMP的next数组计算背模板更要懂原理卷2第一道让我印象深刻的题是给模式串pabacaba要求写出next数组。很多人对这个字符串匹配算法的理解停留在“背代码”阶段导致计算next[i]时经常混淆“最长相等前后缀长度”和“失配后跳转位置”这两个概念。我当时是这么捋清楚的。next数组可以定义为对于模式串的每个位置i通常从1开始next[i]表示在模式串的第i个字符失配时模式串应该回退到的位置。而它的核心计算依据是模式串前缀子串p[0...i-1]的最长相等前后缀长度。以pabacaba为例next[0] -1这是约定初始值失配时从头开始。next[1] 0只有一个字符时没有真前后缀。前缀ab最长相等前后缀长度为0所以next[2] 0。前缀aba最长相等前后缀为a长度为1所以next[3] 1。前缀abac最长相等前后缀长度为0所以next[4] 0。前缀abaca最长相等前后缀为a长度为1所以next[5] 1。前缀abacab最长相等前后缀为ab长度为2所以next[6] 2。前缀abacaba最长相等前后缀为aba长度为3所以next[7] 3。最终next数组为[-1, 0, 0, 1, 0, 1, 2, 3]。这道题真正的陷阱在于“前缀”和“真前缀”的区别。最长相等前后缀不能取整个字符串本身否则你的next值会比正确答案大1后面匹配时跳转就会出错。我建议大家在笔试前一定要亲手推导一遍这个数组而不是只看代码。手推过一次之后你对KMP的记忆会牢固很多。2.2 堆排序手撕建堆与调整的边界卷2里有一道关于堆排序的题要求写出对数组建堆后第一次调整的结果。这种题看着基础但最容易在“根节点下沉”的边界条件上翻车。堆排序的核心是两个操作自底向上建堆以及堆顶与堆尾交换后的向下调整。建堆时从最后一个非叶子节点开始也就是下标为n/2-1的位置0基下标逐个执行下沉操作。这里的关键是下沉时要用左右孩子中较大的那个大顶堆和父节点比较如果孩子更大就交换然后继续向下直到越界。我回忆卷子里给了一个示例数组[4, 10, 3, 5, 1]要求建成大顶堆。建堆过程如下最后一个非叶子节点是下标1值为10。它的孩子是下标3值5和下标4值110已经大于5和1不需要调整。接着看下标0值为4。左孩子下标1值10右孩子下标2值3。选较大的孩子10交换4和10。交换后4来到下标1。继续看它的孩子下标3值5和下标4值1选较大的5交换4和5。最终得到[10, 5, 3, 4, 1]。如果你忽略了“交换后还要继续向下检查”这一步很可能在第一次建堆时得到错误结果。这也是堆排序手写题里最常见的丢分点。关于时间复杂度建堆是O(n)不是很多初学者以为的O(n log n)。原因在于越靠近叶子的节点下沉的高度越小整体求和收敛于O(n)。这个点笔试可能不会直接问但如果你在编程题里用了优先级队列面试官再追问时答得上来说明你是真懂。2.3 排序算法横向对比稳不稳定不是靠背卷2里有一道选择题把冒泡排序、快速排序、堆排序、归并排序放在一起问哪些是稳定排序。这种题的核心不是背结论而是理解稳定性到底是什么意思。稳定性指的是如果两个元素的值相等排序后它们的相对顺序是否保持不变。冒泡排序和归并排序是稳定的因为它们在相等时不会交换位置或会先处理左半部分。快速排序和堆排序通常不稳定因为它们在交换过程中可能跨越远距离移动元素从而改变相等元素的相对顺序。我当时答题时的思考方式是每种排序里相等的元素会被“换位置”吗冒泡排序遇到相等元素时不交换稳定快速排序因为要选基准值并分区相等的元素很容易被换到远处不稳定堆排序在建堆和调整过程中父子节点跨层交换不稳定归并排序合并两个有序数组时优先取左半部分的元素稳定。这类题目其实在考察一个工程师在写排序逻辑时是否清楚自己的比较逻辑会不会影响业务数据的相对位置。比如在处理一组带时间戳的记录时如果你想按优先级排序又不想破坏相同优先级的时间先后关系那就要选稳定排序。笔试里让你算稳定性本质上就是这个场景的简化版。3. 编程题实战两道典型题目的完整解题链路3.1 第一题区间调度变体贪心优先队列编程题第一题是典型的区间类问题我印象中是“给定若干任务的开始时间和结束时间以及一个共享资源求最多能完成多少个任务”。标准的解法是贪心按结束时间排序依次选择结束最早且与上一个已选任务不冲突的任务。但卷2在这个基础上做了变体我记得它加了优先级权重不再是最多任务数而是要求总权重最大。这样就不能简单按结束时间排序贪心了因为可能放弃一个高权重任务去选两个低权重任务反而更优。这个变体有两种解法方向。一种是动态规划把任务按结束时间排序dp[i]表示前i个任务能获得的最大权重。状态转移时要么不选第i个任务dp[i]dp[i-1]要么选第i个任务找到最后一个结束时间小于等于第i个任务开始时间的任务jdp[i]dp[j]weight[i]。查找j可以用二分时间复杂度O(n log n)。另一种是贪心优先队列的思路专门处理“在时间轴上资源复用”的问题。但要注意贪心在这里是有严格适用条件的如果题目给出的权重不是单调关联的贪心结果可能错误。我当时在答题时选择了动态规划二分的方案因为它的正确性更容易证明而且代码量也不大。这道题给我的经验是笔试里看到区间类题目不要一上来就套区间贪心模板。先确认题目是“最多任务数”还是“最大权重”。前者用结束时间排序贪心即可后者往往需要DP。把这两个模型区分清楚就能避免最典型的错误。3.2 第二题带剪枝的搜索题DFS剪枝优化第二题是搜索类问题题目大意是给定一个二维网格每个格子上有数值要求从一个起点走到终点路径上的数字和要满足某个条件问是否存在这样的路径。这就是典型的DFS剪枝题。我当时第一版写的是普通DFS直接对每个方向递归但很快意识到状态数可能会非常大。于是做了两处剪枝可行性剪枝如果当前路径已经超过目标条件比如路径和已经大于目标值就直接返回不再向下探索。这个剪枝在数值都为正数时非常有效能大幅减少搜索空间。重复状态剪枝用visited数组标记当前路径上已经走过的格子防止走回头路形成环路。还有一个常见的优化方向是备忘录剪枝也就是记忆化搜索。可以用memo[i][j][k]表示“在(i,j)位置且当前状态为k时是否还有可能到达终点”。如果这个状态已经访问过且结果是false则直接复用不用重复递归。这种优化在状态空间较大的搜索题中是通用的提速手段。我在做这道题时犯过一个小错误visited数组的回溯时机不对。一开始我在进入递归前就标记visited但在递归返回后忘记恢复导致同一格子在另一条分支里被误判为已经访问过漏掉了正确答案。这个问题在DFS类题目里极其常见如果和邻接矩阵、字符网格类题目结合起来排查起来会很费时间。我建议在笔试前把DFS模板的“标记-搜索-恢复”三步在本地多练几遍形成肌肉记忆。3.3 快速幂与大数据取模容易被忽略的计算细节卷2里有一道关于快速幂的题要求计算某个大数的幂并对一个大质数取模。这个题考查的核心是“如何在计算过程中避免溢出以及如何把指数降低”。快速幂的思想是二分指数要计算a^b如果b是偶数a^b (a^(b/2))^2如果b是奇数a^b a * a^(b-1)。通过递归或迭代可以将时间复杂度降到O(log b)。但笔试里真正容易丢分的是取模的写法。正确的做法是每一步乘法之后都取模而不是等计算出完整结果再取模。如果直接相乘两个int相乘的结果可能超过int范围甚至在语言里溢出成负数。我通常用long类型承接中间结果并在每次乘法后立即模运算即long res 1; while (b 0) { if ((b 1) 1) { res (res * a) % mod; } a (a * a) % mod; b 1; }这道题本身不难但它提醒我大数运算相关的边界条件一定要在代码里先考虑清楚。笔试环境没有本地调试编译器也不会帮你拦截溢出所以设计变量类型时就要用足够宽的类型。笔试前把快速幂、大数gcd、素数判断这类数学类算法准备好性价比很高因为它们每年都会出现在不同公司的卷子里。4. 机器学习与深度学习算法岗笔试中的“跨界”考点4.1 从KNN到聚类基本概念不能停留在“会用”卷2的简答题里有一道关于KNN的题问的是“KNN算法的应用能力包括哪些方面”。这类题如果只回答“分类和回归”虽然不会错但拿不到满分。因为KNN的应用能力可以拆得更细分类场景根据K个最近邻的类别做投票确定样本类别。这是最经典的用法。回归场景根据K个最近邻的目标值取平均作为预测值。异常检测如果样本的K个近邻都很远说明它可能是一个异常点。数据预处理中的缺失值填充用最近邻的特征值填充缺失列。此外KNN还可以用在一个更偏工程的场景里特征选择。通过计算样本间的距离判断哪些特征对区分样本更有用。这种题目在笔试里出现通常不是想听你背API而是想看你有没有把算法用在真实数据上的直觉。从KNN延伸到聚类也一样。卷2里有一道关于聚类的选择题问的是K-Means算法的停止条件。正确答案是质心不再显著变化或达到最大迭代次数。但这里有一个隐含的理解点——K-Means对初始质心敏感不同的初始化可能导致不同的聚类结果这也是K-Means出现的原因。笔试如果继续追问多半会往“为什么K-Means能改善聚类效果”方向走因为它让初始质心尽可能分散减少陷入局部最优的概率。4.2 XGBoost、卡尔曼滤波与RL一道题串起三个模型卷2有一道综合性简答题我记得大意是在一个动态系统的状态估计问题中你拿到了带噪声的观测序列要求选择合适的算法来估计真实状态。这道题表面上看是信号处理问题实际上是在同时考察候选人对卡尔曼滤波、XGBoost、强化学习这三类工具的理解边界。我的答题思路是先给出结论如果系统的状态转移模型和观测模型可用线性高斯模型描述首选卡尔曼滤波。因为它通过预测和更新两步能够在线性高斯条件下给出最优状态估计复杂度低且能在线执行。如果模型是非线性的那可能需要扩展卡尔曼滤波或无迹卡尔曼滤波。然后我解释为什么XGBoost不适合这个任务。XGBoost擅长的是监督学习场景下的表格数据建模它的输出是静态的、离散时间点上的预测值不适合做带时序依赖的在线递推估计。你可以用XGBoost做特征重要性排序但它本身没有“状态随时间递推”的机制。至于强化学习它的目标是学习一个策略来最大化长期收益而不是估计隐藏状态。在状态估计问题上强化学习既没有观测模型也没有状态转移模型可依赖强行使用等同于拿大炮打蚊子既不高效也不稳定。这道题给我的启示是算法岗笔试的简答题不会考你“XXX的公式是什么”而是给你一个场景看你能不能从工具箱里挑出合适的算法并说明理由。这也是平时面试官最爱问的“模型选择”问题的书面版。4.3 图像分类与拉普拉斯算子卷子里的感官题卷2里出现了一道和图像处理相关的题内容是考察图像锐化。这道题出现得挺意外它在选择题里问图像锐化常用的算子是什么选项里有Sobel、拉普拉斯、Canny、高斯模糊。答案是拉普拉斯算子。这个题考察的是对图像处理基础概念的掌握。我当时对这个知识点做了一个简单梳理Sobel算子是边缘检测算子可以用来提取图像中的边缘信息它通过计算水平方向和垂直方向的梯度来实现。拉普拉斯算子是二阶微分算子它能突出图像中灰度的快速变化区域因而常用于锐化。Canny是一种边缘检测算法包含高斯滤波、梯度计算、非极大值抑制、双阈值检测多个步骤。高斯模糊则是低通滤波作用是平滑图像、去除噪声和锐化正好相反。这类题目出现在算法岗笔试里背后的逻辑是图像算法和深度学习算法已经被广泛应用于手机摄影、人脸识别、自动驾驶等领域。小米对算法岗候选人的要求并不仅是会训练深度模型还要理解传统图像处理的基础操作因为这些操作经常被用作深度学习的预处理模块或特征补充。我在复习时通常会把这些图像处理算子的公式和用途放在一张表格里对照记忆像拉普拉斯算子的卷积核是[[0,1,0],[1,-4,1],[0,1,0]]Sobel的水平梯度核是[[-1,0,1],[-2,0,2],[-1,0,1]]。笔试时如果记不清公式至少要知道它们各自的用途和典型应用场景。5. 边缘算法与工程题考的不是算法是系统思维5.1 Kahn算法与Rete规则引擎从拓扑排序到事实匹配卷2里有一道关于拓扑排序的题可以用Kahn算法解决。Kahn算法的思路非常直观每次从图中取出一个入度为0的节点输出它然后删除它出发的所有边更新相邻节点的入度重复这个过程。如果最后输出的节点数小于图中节点总数说明图中存在环。这个算法本身不难但卷2的深意在于把拓扑排序的思想延伸到了规则引擎里。规则引擎Drools使用的Rete算法核心就是在规则条件和事实之间构建一个网络通过节点共享和状态缓存来提高匹配效率。你可以把Rete看成是“事实在规则条件网络上的拓扑排序式传播”事实在网络节点间流动能匹配的条件被逐步激活最终匹配到完整规则。我当时看到这道题时先愣了一下后来才意识到它实际上是在考“图遍历算法的工程应用”。如果你只把Kahn算法当作一道Graph题来准备没有想过它和依赖解析、规则引擎、任务调度之间的关联遇到这种变体就容易发怵。举一个更贴近日常的例子构建系统时多个编译任务之间存在依赖关系Kahn算法就是用来决定“先编译哪个模块、后编译哪个模块”的经典方案。对于一个算法工程师而言能识别出“这个问题其实是一个拓扑排序问题”是比会写Kahn算法的代码更重要的能力。5.2 PID、MPPT与FOC控制算法为什么要进算法卷卷2的压轴选择题里出现了一个让很多刷题型选手措手不及的内容PID、MPPT和FOC。这些词一看像是嵌入式或控制工程领域的术语为什么会出现在算法笔试里其实很简单。算法岗位并不只有推荐、搜索、图像这些方向还有很多岗位面向智能硬件、机器人、新能源设备。比如小米生态链里有大量设备需要做电机控制、电源管理、运动控制这些场景离不开PID、FOC磁场定向控制和MPPT最大功率点跟踪。我当时对这一步知识做了快速梳理PID控制器通过比例、积分、微分三个环节对被控量进行闭环调节。比例项响应当前误差积分项消除稳态误差微分项抑制超调。我在卷子里写的例子是一个电机转速控制场景目标转速3000当前转速2900比例项输出增加积分项逐步累积消除剩余偏差微分项根据误差变化率提前刹车。MPPT在光伏发电等场景中通过调节工作点使输出功率达到最大。常见实现是扰动观察法每次给工作电压一个微小扰动观察功率变化方向如果功率增加就继续同方向扰动否则反向。FOC又叫矢量控制用于电机驱动。它把定子电流矢量分解为励磁分量和转矩分量从而模拟直流电机的控制效果让交流电机拥有更平滑的调速性能。如果你准备的是通用算法岗不太了解这三个概念也没关系。但如果你把小米作为目标公司这部分知识值得提前看。毕竟一家做硬件产品的公司算法笔试里出现控制算法是合情合理的。5.3 SM2、SM3、SM4与ZUC商用密码算法的题目形态有一道网络安全相关的题目我印象也比较深给出SM2、SM3、SM4和ZUC四个算法要求区分它们各自的用途。SM2是非对称加密算法SM3是密码杂凑算法SM4是分组对称加密算法ZUC是流密码算法。这类题目对算法工程师来说看似不是核心考点但大厂在涉及数据安全、身份认证、隐私保护的业务场景里会对候选人是否具备密码学基础知识提出要求。比如推荐系统里用户数据的加密存储、边缘设备与云端通信的认证流程都用得到这些基础概念。我复习这类题目时的经验是先给每个算法归类再用实际场景串联。SM2常用于数字签名和密钥交换类似RSASM3用于生成消息摘要和完整性校验类似SHA-256SM4用于数据加密存储类似AESZUC用于通信领域的加密类似RC4。通过类比的方式记忆比单独背诵每个算法名称的效率高很多。笔试到这里其实已经超出“纯算法”的范畴它更像是在考察候选人是否具备全局视野。算法岗不是只跟模型和数据结构打交道也要理解业务系统里安全、控制、通信这些板块的存在。6. 复盘与避坑这场笔试教会我的几件事6.1 时间分配的失误与调整我做完卷2之后复盘发现自己在编程题上花的时间略多导致最后简答题的论述有些仓促。现在回想如果能把时间分配调整为选择题和填空题控制在40分钟内编程题每道控制在25分钟内简答题留出30分钟整体节奏会从容很多。尤其要注意的是很多选择题看起来简单但计算量不小。比如KMP的next数组推导、堆排序的一次调整都需要在草稿纸上仔细画图。这类题不能靠心算否则很容易因为下标错位丢分。我当时的做法是遇到需要手算的题先在草稿纸上把过程写清楚再填答案这样也方便之后检查。6.2 那些反复出现的易错点把我在卷子里的错误、以及周围同学常见的错误汇总一下主要集中在几个地方KMP的next数组下标定义不一致。有的教材用0基、有的用1基笔试如果没给清楚定义要自己在答题时先约定并写明白。堆排序建堆时忽略“交换后继续下沉”。这是最大的失分点。DFS类的visited状态忘记回溯。这种情况在网格类题目里尤其常见。快速幂中间结果没用long类型导致溢出结果错误。排序稳定性判断凭印象不凭推导导致多选题勾错选项。这些易错点其实都是“练习量不够导致的细节遗漏”。刷题阶段如果只追求AC数量不注重手写推导笔试时就会暴露问题。我在秋招后期养成了一个习惯每道LeetCode题目做完都会在纸上把核心数据结构的变化过程重新演算一遍。这样虽然慢但对原理的掌握程度会明显提升。6.3 后续备战的策略调整经历过这套卷子之后我调整了自己的备战策略。原来的重心几乎全在刷题上后来开始补充三块内容数学思维类算法快速幂、概率期望、组合数取模、机器学习基础聚类、KNN、损失函数设计动机、以及一些跨领域工程概念控制算法、密码学基础、规则引擎。具体来说我会把高频考点分成多个标签每个标签下面收集对应的3道典型题然后定期做“无提示手写推导”练习。和单纯刷题相比这种方式对原理记忆的强化效果好了很多。另外我还会针对目标公司的业务方向做知识补充。比如投小米前特意了解了一下小米在IoT和智能硬件方向的布局把PID控制、MPPT这些概念过了一遍。虽然当时不知道会不会考但最后卷子里真的出现了一题这让我非常庆幸自己做了准备。笔试只是算法岗位应聘流程的第一步即使卷面答得不太理想后面还有面试可以展示自己的思考和潜力。但反过来如果笔试能够发挥出真实水平后面面试时的心态会从容很多。希望这套卷子的拆解能让你在准备大厂算法岗笔试时多一点方向感少踩一些我踩过的坑。
返回列表