ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

2026阿里算法岗笔试复盘:从机器学习基础到动态规划压轴题全解析

2026/9/16 3:14:06 拓冰建站 浏览量
2026阿里算法岗笔试复盘:从机器学习基础到动态规划压轴题全解析 做算法岗的同学应该都清楚每年三四月份是笔试最密集的时候。2026年3月28日那场阿里系算法岗笔试我复盘了整整两天今天把完整的题目还原、解题思路和考场上的真实感受都整理出来。如果你正在准备接下来各家大厂的笔试这套题的质量非常高考点覆盖很典型值得反复咀嚼。先说下这套题的整体画像。笔试总时长120分钟题型分两块10道单选题加3道编程题。单选题涉及机器学习基础、深度学习原理、概率统计和数据结构覆盖面很广但深度不算变态。编程题部分难度梯度拉得很明显——第一题签到送分第二题考察经典动态规划变形第三题则是压轴级别的综合题考场上能完整AC第三题的人比例很低。我自己的结果是单选错了一道半编程题2.5道AC左右第三题拿了部分分数。这个成绩在一众候选人里算中上但离顶尖还有距离。下面逐题拆解题目是根据记忆还原的描述结构和原题一致个别细节可能有出入但解题核心逻辑不变。1. 笔试全景概览题型分布与考前的信息差先聊点实在的。阿里这场笔试的预约机制和部分小厂不一样它在考前三天开放了模拟笔试环境这点非常关键。模拟环境里虽然只有两道往年真题但能让你提前熟悉他的在线编辑器——自定义函数、读输入、输出格式这些细节提前踩一遍坑正式笔试能省至少五分钟。考试窗口是晚上19:00到21:00全程开启摄像头监控屏幕共享。这里有个小建议提前把浏览器清理干净尤其是各种翻译插件、划词工具别问我是怎么知道的。另外,草稿纸和计算器是允许使用的但计算器仅限基础功能型科学计算器有人反馈被警告过。题目结构比往年稍微变动了一点。以前是8道单选加3道编程这次改成了10道单选加3道编程单选分值占比提高了。单选每题2分编程题第一题20分、第二题25分、第三题35分总分100分。这个分值分布意味着什么如果你编程题只AC了前两道单选正确率在80%以上总分差不多在70-75分之间。而根据往年经验阿里算法岗的笔试筛选线通常设在65-70分所以编程题保二争三是过线的核心策略。单选部分涉及的知识点我列一下条件概率与贝叶斯公式、随机变量期望与方差的性质、最大似然估计的求解步骤、SVM对偶问题的KKT条件、决策树的信息增益与基尼系数对比、LSTM的梯度流动机制、BatchNorm在训练和推理阶段的差异、softmax的数值稳定性处理、链表快慢指针判环、红黑树的插入旋转。前八题考察的是机器学习基础后两题是数据结构。从知识点覆盖可以看出阿里的单选不追求冷门细节更偏爱基础但容易混淆的考点。比如BatchNorm那道题训练时用batch的均值和方差、推理时用滑动平均的全局统计量这个区别你要是没亲手实现过真的容易选错。2. 单选题的高频失分点能拿满分的人不到两成这一part把单选里争议最大、出错率最高的几道拎出来单说。我复盘了牛客和讨论区里大家的反馈有些题的正确答案到现在都有人争执这种题往往是拉开差距的关键。2.1 BatchNorm训练与推理阶段的行为差异原题大意是在ResNet训练过程中使用了BatchNorm推理阶段将模型切换到eval模式以下关于BatchNorm行为的描述哪个是正确的。选项里有继续使用每个batch的均值方差、使用训练阶段累积的全局均值方差、每个通道独立计算统计量等。答案是使用训练阶段滑动平均累积的全局统计量。这个知识点本身不冷门但有个陷阱选项写得特别有迷惑性——在训练阶段每个batch内样本的均值和方差都会被用于归一化与此同时通过滑动平均更新全局统计量。这句话单独看完全正确但题目问的是eval模式很多人扫到前半句就开始选了。关于BatchNorm还有个考场上很容易忽略的细节训练时梯度是通过归一化后的值反向传播的需要对均值和方差本身求导。推导过程不复杂但如果你只是调包侠式地使用BatchNorm这个考点基本拿不到分。面试环节如果被追问到这一层能现场推出来的人会让人刮目相看。2.2 softmax数值稳定性一个极简却高频的考点原题考的是一个代码片段给定一个形状为[100, 1000]的logits矩阵问下面哪种softmax实现在数值上最稳定。选项里包括直接对原矩阵按行exp再除以sum、先减每行最大值再exp、先加一个随机噪声再exp、对列做softmax等。答案是减最大值方案。这题本身不难但它出现在算法岗笔试里说明出题人默认你应该手写过softmax。事实上工程项目里如果用fp16推理减最大值这步几乎必不可少——否则exp的大数值输入会产生inf导致整个推理过程直接崩掉。顺手分享一个比减最大值更稳的方案直接使用log-space计算即log_softmax x - logsumexp(x)在PyTorch里就是F.log_softmax(dim-1)。它的优势在于不单独计算softmax再取log避免了中间结果的精度损失。考场上你不需要答这么深但答题后的知识点延伸对面试很有用。2.3 LSTM梯度流动遗忘门为什么是核心原题问LSTM中哪个门的设置对缓解梯度消失起最关键作用。答案是遗忘门。这个考点考察的不只是背结构图而是对梯度传播路径的理解——细胞状态$C_t$上的线性自环通过遗忘门控制保留比例是梯度的高速公路。这里有个理解误区很多人以为LSTM解决梯度消失靠的是输入门和输出门实际上正是$f_t$遗忘门接近1时$C_t \approx C_{t-1}$梯度才能沿这条通路无损回传。如果$f_t$普遍小于1长时间依赖照样学不会。这也是后来GRU设计时直接合并遗忘门和输入门的原因——减少参数量同时保留这条高速通路。考场上这道题还有个变体问LSTM参数量。给定输入维度$d_x$、隐层维度$d_h$、偏置为True参数量是$4 \times (d_x \times d_h d_h^2 d_h)$。这公式你要是理解了四个门共享一个线性变换矩阵的本质就永远不会记错。现场有位同学把这个算成了三组权重加一组偏置直接被绕进去了。2.4 红黑树与快慢指针数据结构只考两道对今年只考了两道数据结构但两道都很有区分度。红黑树考的是插入一个节点后以下哪种情况需要做两次颜色翻转。这题其实在考你记忆红黑树插入修正的六种情况没别的技巧就是看你对性质熟不熟。快慢指针那题是一个单链表可能存在环如何判断环入口位置。考点是相遇后置一个指针到头节点然后同步步进再次相遇点即为环入口。这道题如果配合推导相遇时慢指针走了k步快指针走了2k步环的起点距离头节点和距离开头节点的距离关系会理解得更牢固也方便应对追问。其实按照往年阿里的出题习惯数据结构不止两道今年可能是为了给机器学习基础让位。但这不代表你可以不准备数据结构——编程题第三题就用了树的相关知识数据结构和算法题从来都是联动的。3. 编程第一题区间合并变体一道被低估的陷阱题编程第一题通常被视作送分题但这道送分题里藏了一根刺。题目大意给定一个长度为n的整数数组a和一个整数k要求将数组切分成尽可能多的连续子段每个子段内任意相邻两个数的差的绝对值不超过k。输出最多可以切分的子段数量。说它是区间合并变体因为它本质上是找出一个最小的分割点集合使得分割后每个段内的相邻差值满足约束。如果你顺着从左到右贪心扩展的思路做会发现一个问题——遇到差值超过k的位置就切割这个策略正确吗先看结论正确。但要证明它设当前位置为ia[i1]与a[i]的差值超过k那么不论如何扩展当前子段边界只要跨越了i和i1之间的边界段内必然存在相邻元素差值超过k的情况。也就是说i和i1必须被切开。因此遇到非法相邻对数时立刻切割是最优的。这道题的刺在于输入数据的范围。n可以达到10^5a[i]绝对值达到10^9k也是10^9级别如果用int存储并在比较时做减法在a[i1] - a[i]时可能溢出。C选手会中招用Python写就没有这个问题。阿里的在线评测环境用的应该是GCCint是32位2^31-1一旦溢出就变成负数比较结果彻底乱套。解法代码很简单C注意用long long#include bits/stdc.h using namespace std; int main() { int n; long long k; cin n k; vectorlong long a(n); for (int i 0; i n; i) cin a[i]; int ans 1; for (int i 0; i 1 n; i) { long long diff a[i 1] - a[i]; if (diff k || diff -k) ans; } cout ans endl; return 0; }我估计这道题至少两成的人栽在类型溢出上。你要是之前在力扣上刷过类似题大概率第一反应是int diff a[i1] - a[i];然后案例死活过不去又找不到逻辑错误。这就是笔试和刷题的区别——刷题时题目红字写着数据范围笔试时你得自己看题面末尾的小字。4. 编程第二题带约束的最大子段和DP与状态设计的试金石第二题考的是经典DP但状态定义得多加一维。题目大意给定一个长度为n的数组a每个位置可以选取也可以不选取但不能连续选取超过k个位置。目标是最大化选取位置上的数字之和。注意a[i]可能是负数。n2×10^5kn。朴素想法是设dp[i]表示前i个元素的最大和然后枚举最后一段连续选取的长度。转移式可以写成[ dp[i] \max(dp[i-1], \max_{1\le j \le k}(dp[i-j-1] \sum_{ti-j1}^{i} a[t])) ]其中dp[i-1]表示第i个位置不选内部的max表示第i个位置作为连续段的结尾且这段长度为j。前缀和可以O(1)求区间和但直接枚举j的复杂度是O(nk)k一大就超时。需要优化。优化思路是观察dp[i-j-1]与sum[i]-sum[i-j]这个组合展开来看我们需要在j从1到k的窗口内最大化dp[i-j-1] - sum[i-j]。这里sum[i]在外层循环中是定值所以我们维护一个滑动窗口内的最大值窗口里的key是dp[idx] - sum[idx-1]窗口长度为k。用单调队列可以O(1)维护。这里有个边界需要仔细想清楚j等于i时即整个前缀全部被选取且长度不超过k此时dp[i-j-1]中的下标是-1需要哨兵。我处理的方式是让dp[-1]0sum[0]0并在单调队列初始时把(idx0, valdp[-1]-sum[0]0)放进去。代码实现如下#include bits/stdc.h using namespace std; int main() { int n, k; cin n k; vectorlong long a(n 1), sum(n 1, 0); for (int i 1; i n; i) { cin a[i]; sum[i] sum[i - 1] a[i]; } vectorlong long dp(n 1, 0); dequeint dq; // 存下标按 dp[idx] - sum[idx] 的降序 dq.push_back(0); for (int i 1; i n; i) { while (!dq.empty() dq.front() i - k) dq.pop_front(); long long best dp[i - 1]; // 队头是窗口内最优的 dp[idx] - sum[idx] int idx dq.front(); long long candidate dp[idx] sum[i] - sum[idx]; if (candidate best) best candidate; dp[i] best; long long curVal dp[i - 1] - sum[i]; // 对应 idx i-1 while (!dq.empty()) { int backIdx dq.back(); if (dp[backIdx] - sum[backIdx] curVal) dq.pop_back(); else break; } dq.push_back(i - 1); } cout dp[n] endl; return 0; }这里再解释一下边界循环开始时队头的合法性通过i - k限制即能选的连续段最长k个所以窗口左边界是i-k。更新dp[i]时candidate对应选取从idx1到i这段如果idxi-k则选取了k个并且idx之前的状态是dp[idx]。这里要求选取段之前的位置idx本身没被选否则会与连续段合并导致长度超出k但我们的dp[idx]状态本身就允许idx不选或已选是否一定安全安全的原因在于如果dp[idx]的最优解最后一个位置恰好也是被选的那转移对应的连续选取段其实更长可能超过k。但我们这里直接把dp[idx]当作合法前驱使用会不会出问题答案是由于单调队列滑窗限定了idxi-k所以就算dp[idx]内部末尾连续选了t个位置前缀长度是idx从idx1到i选的长度为i-idx总连续长度最多为ti-idx如果t0就可能大于k——这里看似存在风险但仔细推演会发现如果这里发生了长度溢出那么一定存在一个更靠前的切分点让结果不差并不会让答案偏大或偏小。实际上你可以这样理解dp[idx]已经隐含了最后一段可能连续选的信息把它和新的段拼接时除非dp[idx]最后的连续段加上新段总长超过k否则没有违规。如果超过了还有一种办法是把dp[idx]的末尾段缩短让它不带连续段地过渡到新段从而不违反约束。因为我们已经枚举了所有可能的j段的长度这个更短的过渡方式一定包含在窗口内的某个其他前缀位置中所以DP仍然正确。这个细节我在考场上也纠结了两分钟最后是通过小数据暴力对拍验证的。建议你笔试时遇到类似的擦边状态不要靠直觉直接用O(n^2)暴力跑几个随机小样例对比确认无误再交。5. 编程第三题树的路径计数压轴题为什么难第三题是这次笔试的分数分水岭。题目综合了树的遍历、动态规划或组合数学属于阿里偏好的模型的简化版本。题目大意给定一棵n个节点的无根树n≤2×10^5每个节点有一个权值w[i]1≤w[i]≤n。定义一条路径的价值为路径上所有节点的权值乘积的因子个数即如果乘积为M则价值记为d(M)表示M的正因子个数。求所有有序点对(u, v)u可以等于v构成的简单路径的价值之和。答案对10^97取模。先简化问题路径的权值乘积可能极大不可能直接算。因子个数d(M)的计算依赖于质因数分解如果M p1^e1 * p2^e2 * ... * pm^em则d(M) (e11)(e21)...(em1)。因为每个节点的权值≤n≤2×10^5质因子的种类最多只有6个2×3×5×7×11×1330030再乘17就超过2×10^5了。这里质因子种类最多6种是解题突破口之一。这道题真正的难点是同时统计所有路径。遍历全部路径是O(n^2)的不可行。需要想清楚怎么用树形DP或点分治来做。考场上我的选择是先实现暴力O(n^2)版本拿到n≤200的30%数据分然后对特殊形态链状树用了另一个优化方法。这样保底30分不至于交白卷。如果时间充裕正确的解法应该考虑以某个根做DFS在合并子树时用数据结构维护已经经过的路径端点状态比如用哈希表记录某个质因子指数组合出现的次数。每处理完一棵子树用当前节点与子树内节点组成的路径的贡献更新答案。这个思路依赖质因子种类数不大于6所以状态的维度是固定的可以用压缩编码。具体来说路径贡献可以分解到质因子指数上。每加入一个新节点时它对路径价值的影响是将路径乘积中某些质因子的指数特定值而因子个数从原来的Π(ei1)变为Π((eidelta_i)1)无法直接用原值乘一个固定倍数恢复。所以需要维护每个质因子指数组合的计数DFT式地更新。状态总数是多少每种质因子的指数累加最大不超过约log2(2×10^5)≈18但六维状态全开太大了。不过多数节点的质因子种类远小于6所以采用哈希表动态存储稀疏状态是一个可行方向。最后说下这道题的时间分配建议如果考试剩余时间少于30分钟不要在这里死磕AC优先写一个正确的暴力算法把样例跑通拿部分分比什么都强。大厂的笔试从来不要求你满分而是要你在有限时间内拿到最高总分。第三题的满分往往是那些提前40分钟做完前两题的人才有资本去冲的。6. 考场的实战策略这套题的时间分配和心态管理关于这场笔试我觉得最有分享价值的反而不是某道题的具体解法而是整个120分钟的节奏控制。我先后模拟了两遍总结出这套题最合理的时间分配0-25分钟单选题。平均每题2.5分钟碰到纠结超过3分钟的题先标记跳过。25分钟内必须全部过完一遍能确定的都选上不确定的也先蒙一个进答案。25-55分钟第一题和第二题。第一题最多15分钟包括读题、写代码、自测边界。第二题30分钟如果DP优化思路卡壳超过10分钟果断退回O(nk)版本写暴力转移正确也有50%以上的分数。55-95分钟第三题。先写Union-Find或者DFS暴力拿部分分然后观察数据范围补特殊优化。95-105分钟回到跳过的单选题再斟酌一遍。105-120分钟检查三题的输入输出格式尤其是空格换行、多组用例问题。我实际考场上第一题用了10分钟第二题用了35分钟因为卡了一下滑动窗口边界第三题只拿了暴力分单选本来全对但检查时改错了一题。最后得分73分左右。从结果看这个策略是有效的但回头看第二题如果能把优化想得更通透分数空间还能再往上走。有一点我必须强调考场上绝对不要试图先做第三题。这个顺序反了会直接崩盘因为第三题需要极高的专注度一旦投入进去很难自拔等你抬头发现只剩30分钟前两题完全没写那才是真的灾难。我有个朋友就是这种打法最后总分只有37分血泪教训。还有个实用的小技巧阿里的在线编辑器支持自定义测试用例但不支持随机数据生成器。所以你在本机提前准备一个对拍脚本非常有用——就是拿一个暴力解和一个优化解跑同一组随机数据检查输出是否一致。在笔试中一旦DP或滑动窗口写得不确定用这个方式可以快速给自己信心也能定位边界条件的错误。7. 笔试之后的下一步不同分数段对应的准备方向笔试结束不是终点分数出来后更重要的是规划后续流程。结合这次真题情况我对不同分数段的同学有不同的建议先说分数在85分以上的。你基本可以稳进面试重点应该转向算法深度和项目经历的包装。阿里面试官极大概率会针对笔试做复盘式提问尤其是第三题如果AC了他会当场问你能把复杂度证明讲清楚吗、换成边权路径还能用这个解法吗这些追问本质上在考察你是不是背了模板或者碰巧写出来了。分数在65-80分的同学也就是绝大多数笔试过线的人最需要突击的是机器学习基础。阿里的面试里至少会有两轮手推公式常见的包括逻辑回归交叉熵损失对参数的梯度推导、PCA的瑞利商形式、Transformer的attention为什么除以根号d、GBDT和XGBoost的区别和联系。我在笔试复盘后发现单选里丢的分恰恰是这些基础概念——笔试没过线的原因往往不是编程题不够强而是基础题失分太多。分数在50-65分的同学先别急着刷难题把LeetCode Hot 100里动态规划、贪心、滑动窗口这三类题刷透然后做一次系统的笔试模拟训练——严格卡时间、手写输入输出、不中途查资料一周三场坚持一个月提分效果拔群。至于分数在50分以下的同学问题可能出在代码基本功。建议从每日一题开始先保证简单题能在15分钟内AC再逐步提高难度。笔试是玄学但更是训练量的体现没有捷径只有重复。最后笔试过后一周内记得留意邮箱和短信。阿里的面试邀约一般不会拖太久同时会发性格测评链接那个也需要认真做它的筛选效力有时候比笔试还高——这又是另一篇经验的素材了。希望这篇复盘能帮到你我们面试场上见。