前几天整理电脑翻出一个命名为“阿里算法实习生笔试”的文件夹里面躺着2015年那场笔试的复盘文档。那会儿云计算和大数据刚火起来算法岗的实习生笔试还没像现在这样动不动就是四道hard题但已经能明显感觉到“机器学习 数据结构 概率统计”三板斧的味道。这篇文章我把当年的题型、踩过的坑、以及后来带新人时反复强调的考点做一个完整拆解手把手还原一套贴近真实难度的模拟卷并给出每一步的思考过程和避坑方法。无论你是准备大厂算法实习还是想系统梳理算法基础这份复盘应该能帮你省下不少瞎折腾的时间。1. 整体风格与考查维度2015年算法岗笔试到底在考什么1.1 当年考场上的真实体感很多人以为算法工程师笔试就是纯刷LeetCode实际上2015年阿里这套卷子已经明显偏“研究型”和“工程型”混合。我印象最深的是选择题里居然有四五个都是从论文里抽出来的场景化描述比如“在广告点击率预估场景下正负样本比例严重失衡你会选择哪种评估指标”这种题不靠背靠理解。整套卷子时间大概90分钟题量不算大但我当时最大的感受是每一道题都在逼你“做决策”而不是单纯“算答案”。比如一道KMP题它不会直接让你求next数组而是给一段字符串匹配场景让你算出失配后模式串该跳到哪。这比网上那些背板子的题恶心多了。从考查维度看大致分了四块数据结构与基础算法排序、链表、二叉树、字符串匹配、动态规划、贪心。机器学习与数据挖掘模型推导、损失函数、评估指标、特征工程。概率统计与线性代数条件概率、贝叶斯、期望、矩阵运算。工程与逻辑题海量数据处理、位运算、场景设计。1.2 为什么这套题到今天仍有参考价值2015年之后算法岗面试套路变了不少但核心能力模型没变能不能用数学语言描述问题能不能用工程手段落地模型能不能在资源限制下做权衡。这套卷子恰恰就把这三个问题串起来了。比如它考排序算法不会简单问你“快排时间复杂度”而是问“如果你要在内存只有2GB的机器上排序10GB的日志文件你会怎么设计”。这种题就是把数据结构和操作系统IO结合起来属于后来主流面试题“外部排序”的雏形。所以我一直觉得与其疯狂刷怪题不如把这一套老卷子里的思路吃透底层能力打通了新题也不过是换皮。2. 数据结构与基础算法高频考点拆解2.1 从排序算法看“复杂度不是唯一标准”排序基本是笔试题里的标配。但2015年那场笔试给我最大的教育是不要以为快排永远是对的。有一道选择题问“在近乎有序的数组中以下哪种排序性能最好”选项里有快排、堆排、插入排序、归并排序。很多人条件反射选快排但实际上近乎有序时插入排序的复杂度能逼近O(n)而快排在部分有序场景下如果基准选不好反而可能变成O(n²)。我当时就答错了后来复盘时总结了一套排序选型判断逻辑数据量小几十个以内插入排序最稳写起来简单常数极小。数据量中等且要求稳定归并排序。数据量极大且内存紧张堆排序空间O(1)。绝大多数通用场景快排但要配合随机化基准。还有一个容易踩的坑归并排序的空间复杂度。很多人背“O(n)”但在“链表排序”场景下归并排序空间可以做到O(1)因为链表不需要额外数组来合并。这种“一题两问”的考法在笔试里特别常见表面考排序实际考你对底层存储结构的理解。2.2 KMP不背next数组也能做对理解失配跳转字符串算法里KMP是笔试常客。但有不少人看到KMP就头疼因为next数组的推导容易记混。我先说一个当年学到的土办法与其死记next[i]不如记住“前缀和后缀的最长公共长度”。举个例子模式串 p “abacaba”我们逐个位置看i0子串“a”前缀后缀交集为空next[0]0。i1子串“ab”前缀有“a”后缀有“b”无交集next[1]0。i2子串“aba”前缀有“a”、“ab”后缀有“ba”、“a”最长公共前后缀是“a”长度1next[2]1。i3子串“abac”前缀“a”、“ab”、“aba”后缀“bac”、“ac”、“c”无公共next[3]0。i4子串“abaca”前缀里“a”、“ab”、“aba”、“abac”后缀“baca”、“aca”、“ca”、“a”最长公共前后缀“a”长度1next[4]1。i5子串“abacab”前缀“a”、“ab”、“aba”、“abac”、“abaca”后缀“bacab”、“acab”、“cab”、“ab”、“b”最长公共前后缀“ab”长度2next[5]2。i6子串“abacaba”最长公共前后缀是“aba”长度3next[6]3。所以 next [0, 0, 1, 0, 1, 2, 3]。笔试里如果考KMP它可能不会让你写完整代码而是给你主串和模式串问某次失配后模式串右移几位。你只要记住“右移位数 已匹配长度 - next[失配位置前一个位置的索引]”就能快速解出来。别把next数组的定义搞混了不同教材里有的叫next有的叫prefix但核心含义一样。2.3 贪心和动态规划边界条件是分水岭贪心算法在笔试里往往以“看似简单实则陷阱”的姿态出现。我记得有一道经典题给定一个数组表示每天股票价格只允许买卖一次求最大利润。这个用动态规划做很简单维护一个当前最小值然后不断更新最大差。但同样的场景如果改成“可以多次买卖”但每笔交易有手续费还能不能简单贪心不能。因为贪心策略“只要今天比昨天便宜就买明天比今天贵就卖”在有手续费时可能亏钱。这时候你仔细分析会发现其实可以转化为动态规划用dp[i][0]表示第i天手里没有股票的最大收益dp[i][1]表示第i天手里有股票的最大收益。状态转移是dp[i][0] max(dp[i-1][0], dp[i-1][1] prices[i] - fee) dp[i][1] max(dp[i-1][1], dp[i-1][0] - prices[i])这种题考的就是你能不能判断“局部最优就是全局最优”这句话不成立的情况。笔试卷里经常会有这种“伪贪心”题目你需要写反例来证明贪心不成立。我的经验是拿到题先问自己三个问题——能否拆成子问题子问题是否独立贪心选择是否安全如果有一个答案是否定的就老实用动态规划。3. 机器学习与数据挖掘理论推导比调包重要3.1 从逻辑回归到损失函数推导细节决定成败2015年那套笔试卷里机器学习部分占比很高其中逻辑回归LR几乎是必考。我当时遇到的题是“写出LR的损失函数并推导梯度下降公式。”很多人能写出损失函数但推导时忽略了sigmoid求导的简化过程。LR的损失函数通常写成交叉熵形式$$J(\theta) -\frac{1}{m}\sum_{i1}^{m}\left[y^{(i)}\log h_\theta(x^{(i)}) (1-y^{(i)})\log(1-h_\theta(x^{(i)}))\right]$$其中 $h_\theta(x) \frac{1}{1 e^{-\theta^T x}}$。对第 $j$ 个参数求偏导时关键技巧是先算 $h_\theta(x)$ 对 $\theta_j$ 的导数因为 sigmoid 函数有性质 $h h(1-h)$。最终梯度形式非常简洁$$\frac{\partial J}{\partial \theta_j} \frac{1}{m}\sum_{i1}^{m}(h_\theta(x^{(i)}) - y^{(i)})x_j^{(i)}$$这个形式就是“预测值减去真实值再乘以特征值最后取平均”。如果你在笔试推导时卡壳大概率是sigmoid求导忘了用链式法则。建议考前亲手动推一遍不要只看书因为做题时时间很紧熟练度直接决定你能不能答完。3.2 评估指标的选择准确率不是万能的笔试卷里出现过一道场景题二分类任务中正样本只占1%你要判断模型好坏能不能用准确率正确答案是“不能用”因为猜全为负样本也能有99%准确率。这时候应该看精确率Precision、召回率Recall或者直接看AUC。后来我在实际业务里深有体会推荐场景看重召回率风控场景看重精确率而搜索排序更看重AUC和NDCG。所以笔试考评估指标其实是在考你有没有“业务Sense”。我当时总结了一套速查表场景首选指标原因正负样本极不平衡AUC对类别分布不敏感垃圾邮件识别精确率误判正常邮件代价高癌症筛查召回率漏诊代价远高于误诊排序场景NDCG考虑位置信息3.3 从朴素贝叶斯到集成学习原理和适用场景要一起记机器学习部分还喜欢出朴素贝叶斯的计算题。我记得一道典型题给定若干个词在“正常邮件”和“垃圾邮件”中出现的概率让你判断一封包含若干关键词的邮件属于哪一类。这种题就是直接套贝叶斯公式但要注意拉普拉斯平滑——如果某个词在训练集中没出现过概率为0会直接让整体变成0。另外集成学习在当年的卷子里开始冒头。比如问“随机森林的基学习器之间相关性如何降低”其实就是随机构建样本子集和特征子集。这个考点直到今天仍然高频率出现值得重视。4. 工程与智力题海量数据处理和场景设计4.1 海量数据TopK从堆到哈希分桶“10亿个整数中找出最大的100个”这种题基本是算法工程师笔试的“赠品”。最常规的做法是维护一个大小为100的最小堆遍历数据时如果当前元素比堆顶大就替换堆顶并调整堆。时间复杂度O(n log K)K很小的时候近似O(n)。但笔试如果只答这个可能只能拿一半分。因为面试官其实想听你怎么处理“10亿个数不能全部加载到内存”这个问题。这时候你需要继续往下说如果内存只能加载一部分就用哈希映射把大文件拆分成多个小文件。对每个小文件内部堆排序得到局部TopK。然后再把所有局部TopK归并得到全局TopK。拆文件时要注意哈希函数的均匀性否则某个文件还是太大。我当时在考卷上写了“取模分桶”但没考虑可能桶分布不均其实更好的做法是用一致性哈希的思路或者先用采样估算数据分布。这些都是后话但笔试如果能把这一层提出来会显得你考虑问题很全面。4.2 位运算的奇技淫巧不用临时变量交换两个数有一道印象深刻的题“不借助临时变量交换两个整数。”学过位运算的都知道用异或a a ^ b b a ^ b a a ^ b但笔试不只是考这个结论它还会问“为什么异或不会丢信息”。关键原因是异或运算满足交换律和结合律且一个数异或两次同一个数会还原。实际工程里我们很少这么写因为可读性差而且在高性能场景下编译器优化后不一定更快。但笔试考它是在考你有没有深入理解底层的“位”概念。同样类型的还有“判断一个数是否是2的幂”“统计二进制中1的个数”。这些题很吃位运算敏感度如果你的状态压缩DP还没搞熟建议先把这些基础位操作练透。4.3 系统设计题从设计一个限流器说起2015年那套题里已经有“设计一个短URL系统”这种题了但它更偏向“算法设计”比如“如何生成不重复的随机短码”。有人可能会想到用随机字符串然后查库去重复杂度高更好的做法是用自增ID加Base62编码或者用发号器预分配一段ID区间。当时我在这类题上吃过亏总想复杂其实面试官只是想看你能不能把问题分解。比如限流器设计一般要聊清楚是固定窗口、滑动窗口还是令牌桶滑动窗口会有边界问题吗令牌桶的消耗速率如何设置如果你能把其中一种方案画出来并给出伪代码这道题基本就稳了。5. 模拟卷实战五道经典题目的完整解析考虑到很多人想要一份可以直接练手的卷子我根据当年的考查风格整理了一套模拟卷的精选部分每道题都附上我的做题思路和参考答案。不建议直接背答案更建议先自己动笔写一遍再对着看。5.1 题目一单链表判断是否有环并找出入口题目描述给定一个单链表判断是否存在环如果存在返回环的入口节点。我刚学这道题时也觉得难但后来发现用快慢指针特别好记。慢指针每次走一步快指针每次走两步。如果有环它们一定会在环内相遇。关键公式是相遇后让慢指针回到head快指针保持在相遇点然后两个指针都每次走一步再次相遇的地方就是环的入口。证明逻辑不复杂设链表头到环入口距离为a入口到相遇点距离为b相遇点继续走到入口距离为c且环长度Lbc。慢指针走了ab快指针走了abkL由于快指针速度是慢指针2倍有2(ab)abkL得到abkL。所以a kL - b (k-1)L c。当k1时ac也就是从头出发和从相遇点出发的指针会同时到达入口。笔试里写代码时不要漏掉空链表和单节点的边界判断。一个稳定写法是def detectCycle(head): slow fast head while fast and fast.next: slow slow.next fast fast.next.next if slow fast: slow head while slow ! fast: slow slow.next fast fast.next return slow return None5.2 题目二最长公共子序列题目描述给定两个字符串str1和str2求它们的最长公共子序列长度。动态规划的状态转移是经典中的经典。设dp[i][j]表示str1前i个字符和str2前j个字符的最长公共子序列长度。如果str1[i-1] str2[j-1]dp[i][j] dp[i-1][j-1] 1。否则dp[i][j] max(dp[i-1][j], dp[i][j-1])。边界条件是dp[0][j]0dp[i][0]0。笔试时写这个题很多人喜欢使用二维数组但要注意字符串长度如果到5000二维数组就是2500万个int内存可能爆。优化办法是用滚动数组因为dp[i]只依赖dp[i-1]这一行。空间复杂度可以从O(mn)降到O(min(m,n))。5.3 题目三二分查找的变体寻找第一个大于等于target的位置题目描述在一个有序数组中返回第一个大于等于target的下标如果不存在返回数组长度。这题看似人畜无害却是我当年笔试失分最严重的地方。很多人直接写while l r然后if nums[mid] target: r mid else l mid 1但如果数组为空没有处理或者初始r设置为n-1导致查找范围少了一个就会出错。更稳妥的写法是使用左闭右开区间def lower_bound(nums, target): l, r 0, len(nums) while l r: mid (l r) // 2 if nums[mid] target: r mid else: l mid 1 return l记住一个口诀找左边界的二分用收缩右边界找右边界的二分用收缩左边界。笔试时先在草稿纸上把区间表示写出来再动代码能减少很多错误。5.4 题目四硬币找零问题完全背包题目描述给定不同面额的硬币coins和一个总金额amount求凑成总金额所需的最少硬币个数。每种硬币数量无限。这是一道典型的完全背包变体。设dp[i]表示金额i需要的最少硬币数初始化dp[0]0其余为正无穷。状态转移是for coin in coins: for i in range(coin, amount 1): dp[i] min(dp[i], dp[i - coin] 1)外层遍历硬币内层正向遍历金额恰好体现“每种硬币可以用多次”的完全背包语义。如果是01背包每个硬币最多用一次内层就要倒序遍历。很多人把这两个循环顺序记反了导致答案错误。我的建议是不要死记而是自己推导一遍正向遍历时dp[i-coin]可能是刚被当前硬币更新的所以能重复选择于是保持了“无限取用”的性质。5.5 题目五从贝叶斯公式到垃圾邮件分类题目描述已知一封邮件中有“免费”和“发票”两个词判断这封邮件是垃圾邮件的概率。已知垃圾邮件中“免费”出现的概率是0.3“发票”出现的概率是0.4正常邮件中“免费”出现概率是0.05“发票”出现概率是0.01。垃圾邮件先验概率为0.1。在朴素贝叶斯假设下两个词独立计算P(垃圾|免费,发票)。先算联合概率P(免费,发票|垃圾) 0.3 * 0.4 0.12P(免费,发票|正常) 0.05 * 0.01 0.0005然后P(垃圾|免费,发票) P(免费,发票|垃圾) * P(垃圾) / (P(免费,发票|垃圾)*P(垃圾) P(免费,发票|正常)*P(正常)) 0.12 * 0.1 / (0.120.1 0.00050.9) 0.012 / (0.012 0.00045) 0.012 / 0.01245≈ 0.9639这题如果不做拉普拉斯平滑一旦遇到某个词在训练集中没出现过概率就会变成0所以笔试里最好主动提一下平滑处理。我在答卷时专门写了“实际工程中会对从未出现的词做平滑避免概率为0”阅卷老师应该会喜欢这种“产品思维”。6. 备考经验与避坑指南从这套卷子里沉淀下来的方法论6.1 刷题策略别用战术勤奋掩盖战略懒惰很多人准备算法岗笔试就是闷头刷LeetCode一天十几道但遇到新题还是不会。我的经验是刷题在精不在多每道题至少要问自己三个问题——这道题的核心思想是什么它属于哪一类模型动规、贪心、二分、图论如果改变一个约束条件解法会不会变比如做股票买卖题时可以把“只允许一次交易”“允许无数次交易”“每次交易含手续费”“有冷却时间”这四种变体放在一起对比。你会发现它们本质都是动态规划只是状态转移方程略有不同。这样打通的刷法一道题顶十道。6.2 数学推导是机器学习的“题眼”机器学习考点里逻辑回归、SVM、朴素贝叶斯、K-means是当年笔试的高频。这些模型光会调包没用一定要能手推。建议把每个模型的以下内容写在纸上目标函数、损失函数、优化方法梯度下降/坐标下降/EM、推导过程、优缺点。考前一周每天默写一遍基本就不会忘了。我记得当时还用白板给自己讲了一遍SVM的KKT条件虽然笔试没考那么深但后来面试时被追问反而成了加分项。所以别觉得推导浪费时间这是长期复利。6.3 时间分配选择题别犹豫大题留足时间整套卷子我最后悔的是在一两道偏题上耗了太久导致后面一道动态规划大题只写了一半。后来带很多新人参加笔试发现大家最容易犯的错误就是“死磕一道选择题”。我的建议是选择题每道最多2分钟没思路先标记跳过。编程题或推导题看到后先花1分钟设计思路和复杂度再动手。如果时间只剩15分钟优先把思路和伪代码写上去不追求完整跑通阅卷人能给步骤分。这套“先抢分再优化”的策略帮我后来在几场笔试里都把完成度提到了90%以上。6.4 关于冷门算法了解思想比记忆代码更重要热搜词里出现了粒子群算法、模拟退火、卡尔曼滤波、PID算法等这些在2015年阿里笔试里几乎没有直接出现但它们属于“如果你会会很加分的边缘考点”。比如一道开放式题“如何解决非凸优化问题”你如果能在梯度下降之外提一句模拟退火或粒子群算法的思想就能展现知识广度。我处理这类知识的方式是看懂核心思想不背代码。粒子群的核心就是“个体历史最优”和“群体历史最优”共同牵引速度更新模拟退火的核心是“以一定概率接受比当前差的解”从而跳出局部最优。理解这些思想后即使笔试不考面试聊到优化方法时也能接上话。7. 写在最后一点个人复盘心得这套2015年的卷子放到现在难度不算高但我觉得它最厉害的地方在于“考点密度大、场景感强”。当年我笔试成绩不算拔尖但正是那次失利让我意识到算法工程师不能只当“刷题机器”一定要把数学、数据结构、业务场景串成一条线。后来我开始尝试在学每个算法时都问自己“这个算法能解决什么真实问题”效果比单纯刷题好很多。如果你正在准备算法岗笔试不妨把这份复盘里提到的知识点逐个过一遍基础扎实了不管出什么新题你就都有了拆解的底气。