2017年阿里内推的算法工程师(运筹优化)笔试题,放到今天来看依然很能说明问题。那几年正是互联网公司开始认真对待运筹优化方向的时候,阿里在电商、物流、调度、定价这些场景里积累了大量的业务需求,急需能把数学建模和工程实现打通的人。我当时认真准备过这一轮,也收集了不少同届朋友的回忆版本,整体感觉是:题量不算夸张,但覆盖面很杂,从机器学习基础到运筹学经典模型,再到纯工程数据结构题都有涉及,是一种典型的“内推笔试先看你的知识底盘够不够宽”的路子。
这篇文章我不打算做所谓的“真题答案汇总”——毕竟时间过去很久,网络上的回忆版本也残缺不全。我更想梳理的是这类笔试背后的考察逻辑、核心知识模块、以及我当时实际踩过的坑和总结出的应对思路。对正在准备互联网大厂运筹优化/算法岗位的同学,应该比单纯背题更有参考价值。
1. 运筹优化岗笔试的第一道坎:先搞清楚它考的是“复合型人才”
先说一个很多人容易误解的地方:运筹优化算法工程师的笔试,并不是只考线性规划、整数规划、网络流这些运筹学内容。2017年阿里那套题(包括内推和校招正式批)的风格是:机器学习基础、运筹优化模型、算法与数据结构、智力题,四块内容混在一张卷子里。
我后来复盘,这其实反映了这类岗位的真实工作状态。在一个电商或本地生活平台里做运筹优化,日常很少遇到纯粹“教科书式”的优化问题。更多时候是:业务方给你一句话需求——比如“帮我把大促期间的仓库拣货波次排一下,让产能尽量用满”——你要做的第一步不是建模型,而是先理解数据、做预测(件量预测)、然后才是建模和求解,最后还要写工程代码把方案部署上线。所以笔试必须同时试探你的模型能力、机器学习素养和编码功底。
那套题的具体结构大致如下(综合多人回忆,不完全精确):
| 模块 | 大致占比 | 考察内容 |
|---|---|---|
| 机器学习/统计基础 | 25%左右 | 损失函数、正则化、交叉验证、偏差方差、常见分类器原理 |
| 运筹优化模型 | 40%左右 | 线性规划建模、指派问题、最短路/最大流、动态规划、贪心策略 |
| 算法与数据结构 | 25%左右 | 排序、链表、二叉树、复杂度分析,部分题需要手写代码 |
| 智力题/数学题 | 10%左右 | 概率题、逻辑推理、数学归纳 |
这个结构放到今天依然没太大变化。哪怕你已经是个有几年经验的工程师,回头再看这套卷子,依然能感受到出题人想找的不是“偏才”,而是“能把优化问题从业务描述一路落地成线上系统的通才”。
2. 运筹学核心模块:建模能力比会背算法更值钱
笔试里真正拉开差距的,不是你会不会用求解器,而是你能不能把一道文字描述的业务问题拆解成规范的优化模型。这也是运筹优化岗位和普通算法岗最大的区别。
2.1 线性规划:考察重点不是单纯求解,而是建模
线性规划在笔试中通常不会让你手动做单纯形法——那个过程又长又容易算错,出题人也没那么无聊。更常见的是给你一个场景,让你写出决策变量、目标函数和约束条件,或者给你一个简单模型,让你判断对偶问题什么样、影子价格的经济含义是什么。
我当时遇到的一个典型题目场景是:某仓库有多个拣货员,每个拣货员在不同波次作业效率不同,如何安排班次使得总拣货成本最低。这就是一个典型的指派问题(Assignment Problem),本身是0-1整数规划,但因为它满足完全幺模性,线性松弛后的解恰好是整数解。这类题目出题人真正想考察的是你知不知道:
- 决策变量怎么定义(这类问题通常定义 (x_{ij}) 表示第 (i) 个拣货员是否被分配给第 (j) 个波次)
- 约束条件怎么列(每个波次必须且只能由一个拣货员负责;每个拣货员的工时限制)
- 能不能判断模型性质(为什么这个问题可以用匈牙利算法或线性规划求解,而不会产生小数解)
关于线性规划,我觉得需要额外多讲两个点。第一个点是强对偶定理。很多同学能背出“原问题和对偶问题的最优值相等”,但真到用的时候会懵。比如题目问“某个资源约束的影子价格是3,代表什么含义?”其实是在问:这个约束对应的对偶变量值等于多少,以及如果该资源增加一个单位,目标函数最优值会提高多少。这类题目不是考察计算,而是考察你对灵敏度分析的理解。第二个点是松弛问题。比如一个整数规划问题,先做线性松弛,然后根据松弛解判断原问题解的上界或下界,这是分支定界法的理论基础。笔试如果出这类题,通常会给一个非常小的二维问题,让你手动迭代两三次分支定界过程,考察的是你对算法逻辑的熟悉度。
2.2 网络流与图论:快递路径优化的默认考点
阿里的业务里,物流网络优化、路径规划、仓配资源调度都是运筹优化的主战场,所以图论和网络流几乎是必考。
最短路问题是送分题级别的存在,Dijkstra 和 Bellman-Ford 要能默写,同时要清楚各自适用的场景:Dijkstra 不能处理负权边,Bellman-Ford 可以,但如果图中存在负权环则无解。这里有个笔试容易踩的坑——题目给你一个带负权边的图,问你“下面哪个算法能求出最短路”,有人会条件反射选 Dijkstra,这就是没理解它的本质原理:Dijkstra 的核心是贪心,每次选当前距离最小的点作为确定点,一旦选了负权边,后面可能发现一条更短的路径绕回来,这个点的“最短距离”就不再成立。
最大流问题也是常客。基础版的 Ford-Fulkerson、Edmonds-Karp 和 Dinic 要了解,更重要的是理解最小割最大流定理——很多看似和流无关的问题,比如二分图最大匹配、项目选择的收益最大化问题,都可以建模成最大流/最小割来求解。我记得比较深的一道题是:有一批订单,每个订单可以选择在两个仓库之一发货,每个仓库有处理上限,求最多能处理多少订单。这本质上就是一个二分图匹配问题,用最大流建模非常自然:源点连所有订单,容量1;订单连可选仓库,容量1;仓库连汇点,容量为该仓库处理上限。跑一遍最大流就得到答案。这类题目出题人考察的不是你背的板子有多熟,而是你能不能看出来“这个问题可以用流来建模”。
另外最小生成树偶尔也会出现。Kruskal 和 Prim 必须要懂,而且要知道两者的应用场景差异:Kruskal 适合边稀疏的图,因为它的瓶颈在排序边;Prim 适合边稠密的图,适合用优先队列优化。这类题一般都比较直白,属于不丢分的基础题。
2.3 动态规划:这里的题往往不再是“纯套路”
动态规划是笔试里区分度比较高的部分。常规的背包问题、最长上升子序列、编辑距离这些,属于大家都会准备的版块,但运筹优化岗位的笔试题,喜欢把动态规划和实际业务场景结合。
举个例子,我当时遇到的一道题:某配送员一天内有若干订单,每单都有一个最晚配送时间,假设配送员每个时间点只能处理一个订单,且每个订单的处理耗时相同,问最多能完成多少订单。刚一看有点像“任务调度问题”,但如果所有任务时长相同,那这个问题其实有一个非常经典的贪心解法:按截止时间排序,优先做截止时间早的。可如果处理耗时不同,贪心就不行了,需要动态规划或带权区间调度。
这种题目的意义在于考察你能不能识别“这是个什么类型的问题”,而不是考察你会不会套某个模板。我当时准备时总结了一个判断链:
- 最优解结构是否具有无后效性?——决策只影响后续状态,不受前面决策具体过程影响。
- 是否满足最优子结构?——子问题最优解能推出原问题最优解。
- 状态空间是否能被压缩?——如果能用一维或二维数组装下,动态规划大概率可行。
- 贪心是否也成立?——如果贪心成立,通常意味着问题有特殊结构(比如拟阵),这时直接用贪心更简单。
这个判断链在笔试里非常实用。因为运筹优化的题目往往披着一层商业场景的外衣,核心其实是经典的组合优化问题,你有意识地做“问题归类”,就能快速定位解法。
3. 机器学习题:大部分是基础题,但“深化一点”你就容易翻车
2017年那会儿,机器学习在算法工程师笔试里的地位已经非常稳固。对于运筹优化岗位,机器学习题通常不会考到深度学习那么深,更多集中在经典模型和理论基础。
3.1 损失函数与正则化:几乎每次笔试都见
逻辑回归的损失函数为什么不用均方误差而用交叉熵?这个问题几乎是所有算法岗笔试题的常青树。我当时准备时把它彻底搞清楚过,核心点有两个:一是均方误差配合 Sigmoid 函数会导致非凸优化问题,因为 Sigmoid 的导数在饱和区趋近于0,梯度下降容易陷入局部最优或收敛极慢;二是从概率解释角度,逻辑回归本身就是在做伯努利分布的最大似然估计,交叉熵就是负的对数似然,它有清晰的概率意义。
正则化部分,L1 和 L2 的区别是必考题。我答题时的策略是抓住本质:L2 正则化对大的参数惩罚更重,倾向于让参数均匀地变小但不等于零;L1 正则化在零点不可导,其最优解往往落在坐标轴上,产生稀疏解。最好的回答方式是补充几何直觉——L1 的约束区域是菱形,与等值线的交点更容易落在坐标轴上;L2 的约束区域是圆形,交点通常在坐标轴附近但不落在坐标轴上。如果你能把这个几何解释画出来或者清晰地描述出来,这道题基本就稳了。
3.2 偏差与方差:千万别只背结论
偏差-方差分解当时几乎每套题都有,2017年那套也不例外。题目通常问法有:为什么决策树要剪枝?为什么随机森林能降低方差?为什么 Bagging 减少方差而 Boosting 减少偏差?
这里我要特别提醒一点:很多人背结论背得很溜,但一旦题目换了个问法就露馅。比如“增加训练数据能降低偏差还是方差?”正确的回答是:增加数据量通常降低方差,因为模型拟合的随机性随着样本增加而降低;但对偏差的影响不大,因为偏差主要来自模型本身的表达能力不足。如果你只会背“Bagging 减方差、Boosting 减偏差”,遇到这种变形题就废了。
我的准备方法是用一个实际案例把所有概念串起来:用一个二阶多项式拟合一个正弦函数,如果只用两个样本点,拟合结果可能千奇百怪——这是方差大;如果用很多样本点但模型只是一条直线——这是偏差大。理解了这两个极端,偏差方差所有变形题都能应对。
3.3 交叉验证与过拟合:出题人喜欢在这里设陷阱
K 折交叉验证在笔试里出现频率极高,而且出题人很喜欢考察“为什么不能用测试集来调参”。我记得有一道题特别典型:模型在训练集上准确率99%,在测试集上准确率70%,问应该怎么办?选项里有“增大训练数据”“降低模型复杂度”“做特征选择”“以上都可以”等。正确答案是“以上都可以”,但很多同学只选了“降低模型复杂度”——这恰恰暴露了对过拟合的理解停留在表面。过拟合的本质是模型把训练数据中的噪声也学进去了,解决办法可以是增加数据量(让模型更难以记忆噪声)、降低复杂度(减少模型容量)、正则化(限制参数空间)、特征选择(去掉无关噪声特征),甚至集成方法。
这些机器学习基础题,看似和运筹优化没什么直接关系,但实际工作中关系极大。我在阿里的朋友后来做仓配网络优化时,第一步永远是预测单量——用时间序列也好、GBDT也好,你得先把预测结果作为优化模型的输入参数。运筹优化工程师如果完全不懂机器学习,连参数从哪来的都说不清楚,这在笔试和面试里都是减分项。
4. 算法与数据结构题:作为运筹优化工程师,代码能力是隐形要求
说实话,2017年看到卷子里出现数据结构题的时候,我是有点意外的,但后来想想也在情理之中——内推笔试的流程往往统一走技术笔试通道,算法题不可避免。而且运筹优化工程师不是纯研究员,方案算出来之后需要自己写工程代码实现,至少要有能力把求解结果嵌入到业务系统里。
4.1 手写代码的常见题型
那套题里出现的算法题,据说不同年份、不同批次的题目不完全一样,但常见的有:
- 单链表反转、判断链表中是否有环
- 二叉树的前中后序遍历、层序遍历、求深度
- 快排、归并排序以及它们的时间复杂度分析
- 二分查找及其变体(比如在旋转数组里查找目标值)
链表反转这道题几乎是我当时所有算法岗笔试的“开胃菜”,虽然简单,却能快速筛掉代码基本功不扎实的人。我的建议是迭代法和递归法都要熟练,最好能直接用文字描述出每一步指针的变化过程——因为在笔试答题时,有时候没法写完整代码,用伪代码加文字描述也能得分。
二叉树的中序遍历迭代实现是另一个提分点。能写出来的人不多,但它能有效区分“背过模板”和“真懂栈模拟递归原理”。核心逻辑是:一直往左走压栈,左子树空了就弹栈输出,然后转向右子树。这个逻辑搞明白了,前序和后序遍历的迭代版本也就容易推了。
4.2 复杂度分析:不只是“背答案”
时间复杂度分析是每家公司的必考题,但运筹优化岗的笔试题可能问得更有水平:比如给你一个回溯法求解的代码,问你最坏时间复杂度是多少、能不能用剪枝优化,甚至让你给一个下界估计。
我记得比较深的是背包问题的复杂度问题。0-1背包的经典动态规划解法时间复杂度是 (O(nW)),其中 (W) 是背包容量。很多同学直接回答“多项式时间”,这是不对的——如果 (W) 是输入的二进制长度(即数值本身),那么 (O(nW)) 实际上是伪多项式时间。这概念听起来有点抠字眼,但出题人很爱在这种地方设陷阱。它的本质是:当问题的参数包含一个大整数时,输入规模是指数级的,那 (O(nW)) 自然就是指数级的。这个点我当时专门研究过,因为它是“P vs NP”讨论里一个非常重要的认知基石——组合优化问题之所以难,往往不是因为我笨,而是因为这些问题的输入中藏着一个数值巨大的参数。
4.3 排序算法的选择:和业务场景联动
排序算法不仅是数据结构题,它和运筹优化场景也有关系——比如在任务调度里,经常需要对任务按截止时间、处理时长、优先级等多个维度排序,选择不同的排序算法会影响整体效率。
笔试里常见的问法是:数据量很大、内存放不下怎么办?这让你联想到外部排序和归并排序——归并排序的一大优势是天然适合外部排序。还有一类问法:数据基本有序,用什么排序最快?答案是插入排序——它的最好时间复杂度是 (O(n)),而快排在基本有序的数据上反而会退化成 (O(n^2))。这些小细节,都能体现你写工程代码时对数据规模的敏感度。
5. 智力题与数学题:概率直觉和建模思维的分水岭
智力题和纯数学题在这类笔试题里占比不大,但很有意思。它们不太考察死记硬背的知识点,而是考察你在现场能不能快速把陌生问题用数学模型表达出来。
5.1 概率题:重点是不重不漏地计数
常见的概率题包括:抛硬币直到出现连续两次正面所需次数的期望;两个人约定在某个时间段到达某地,先到者等XX分钟,求两人相遇的概率;从1到100中随机取数,取到能被3或5整除的数的概率。
这类题目核心是枚举和统计能力。以“抛硬币直到连续两次正面”为例,最稳妥的办法是设状态、列期望方程。根据当前已经连续几个正面,设 (E_0) 为尚无连续正面时的期望次数,(E_1) 为已有一个正面时的期望剩余次数。然后列方程:
- (E_0 = 1 + \frac{1}{2}E_0 + \frac{1}{2}E_1)(第一次抛,正面进入E1状态,反面回到E0状态)
- (E_1 = 1 + \frac{1}{2}E_0 + \frac{1}{2} \times 0)(如果抛到正面,游戏结束;反面回到E0)
解得 (E_0 = 6)。这个“设状态列方程”的思路,其实和动态规划极其相似。笔试里遇到概率期望题,优先尝试列期望递推方程,往往比硬算概率分布更快。
5.2 数学建模与逻辑推理:从描述到符号的转化能力
还有一类题是纯逻辑推理,比如“A说B在说谎,B说C在说谎,C说A和B都在说谎,问谁说真话”。这种题看起来像公务员考试题,但它其实考察的是布尔逻辑建模能力:把每个人的话抽象成布尔表达式,然后验证每种真假组合是否自洽。
如果你把这种题当成娱乐题,那就低估出题人意图了。运筹优化工作中最核心的一项技能,就是把模糊的业务语言“如果……那么……”“最多”“至少”“同时满足”这些表达翻译成约束条件。逻辑推理题锻炼的正是这种“翻译”能力——从自然语言到形式语言,再到求解验证。
5.3 经典的“n个球放m个盒子”问题
这类计数题也常出现,包括球是否相同、盒子是否相同、是否允许空盒,这四种条件组合出16种情况。笔试阶段一般不考非常复杂的组合计数,更多是考基础排列组合公式的应用和容斥原理。
我准备这类题的经验是:不用背全所有情况的公式,而是掌握两个基础工具——插板法和容斥原理。插板法解决“相同球放入不同盒子,不允许空盒”的计数问题;容斥原理解决“至少有一个盒子为空”之类的问题。有了这两个工具,绝大部分组合计数题都能现场推导出来。
6. 运筹优化笔试的备战路线和刷题策略
如果说前五部分是对考试内容的拆解,那这一部分我想重点讲讲备战方法。毕竟知道考什么和能考好是两回事,中间隔着的是系统性的训练和刻意练习。
6.1 建立“模型库+题型库”的双层知识结构
我当时准备时,建了一个文档,分为两层:第一层是经典优化模型库,包括线性规划、整数规划、指派问题、网络流、最短路、最小生成树、动态规划的经典模型变体;第二层是这些模型的典型应用场景对照表。
举个例子,一看到“多个人做多件事,求成本最小”——先想到指派问题;一看到“在图中选若干条路径覆盖所有节点”——先想到路径覆盖,可以转化为二分图匹配;一看当“若干订单有截止时间,求最大完成数”——先想到贪心的日程安排或动态规划区间调度。这种“场景-模型”的快速映射,能显著提升笔试做题速度。运筹优化岗位的题往往披着一个复杂的业务外衣,底层却是经典模型,能一眼看穿包装的选手,答题效率自然高。
6.2 手推经典算法,拒绝“只调包”
一个比较容易忽略的点是:笔试中偶尔会要求你给出算法的迭代过程,尤其是分支定界、匈牙利算法这类。所以光知道用 Python 调scipy.optimize.linprog或者ortools是不够的,你需要能手动推一遍小型算例。
我是这样练习的:找一道只有3x3或者4x4规模的指派问题,手动执行匈牙利算法的行列归约、试指派、加标记、调整矩阵的完整流程;再找一道只有两三个变量的整数规划题,手动跑分支定界,画出分支树并标出每个节点的松弛解和上下界。这套练习非常枯燥,但价值极高——它强迫你理解算法每一步在做什么、为什么这样做,而不是把算法当作黑盒。笔试和面试中能清楚说明算法中间过程的人,给面试官的印象会好很多。
6.3 机器学习基础不能松:重点复习六大块
针对运筹优化岗的笔试题风格,机器学习基础建议重点复习以下六块:
- 线性回归与逻辑回归:损失函数、梯度下降推导、正则化。
- 决策树与集成学习:信息增益、基尼指数、Bagging 与 Boosting 的差异。
- 支持向量机:软间隔、核函数的作用、对偶问题的形式。
- 聚类:K-Means 的流程、K 值选择、优缺点。
- 模型评估:交叉验证、ROC/AUC、精确率和召回率、F1。
- 特征工程:归一化、离散化、one-hot 编码的适用场景。
这六块不需要你达到能深入推导前沿论文的程度,但基本概念和原理必须能清晰表达。每一块都对应一个经典的笔试题源,比如 SVM 必问“核函数的作用”,决策树必问“信息增益是什么”,模型评估必问“什么时候用 AUC 而不是准确率”。
6.4 算法题刷题:LintCode/LeetCode高频题足矣
数据结构和算法部分不需要去啃太偏的题。我的建议是:链表基础操作、二叉树遍历、二分查找、排序、栈队列、哈希表、简单的动态规划(爬楼梯、最大子数组、背包问题),这些类别各刷20~30道就够应付绝大多数笔试题了。
重点不在于刷题数量,而在于每一道题都能用白板讲清楚思路、复杂度和潜在的坑。我当时每次做完一道题,都会在代码旁边标注:这道题如果用暴力法怎么做、时间复杂度多少、优化点在哪里。这个过程帮助我在笔试中面对代码题时,能很快写出一个“先暴力后优化”的答题轨迹,这是面试官和阅卷人都喜闻乐见的。
7. 考后复盘:我总结出的几条实在建议
笔试结束后,我回看自己当时的答题过程,有几点感触比较深,写在这里供大家参考。
第一,学会“战略性放弃”。运筹优化笔试题的覆盖范围实在太广,任何人都不太可能拿满分。遇到卡壳超过5分钟的题,先标记跳过,把后面确定能得分的题做完再说。我是按照“先做会做的,再做有思路的,最后啃硬骨头”的顺序答题的。尤其是在内推笔试这种相对宽口径的筛选中,整体得分比单题满分重要得多。
第二,笔试中的建模题,答案不唯一时不要慌。很多运筹优化建模题没有标准答案——比如让你为一个电商仓库设计货架摆放的优化模型,每个求职者的决策变量定义、约束条件、目标函数选择都可能有差异。阅卷人通常关注的是你的模型是否自洽、约束是否完整、目标是否可求解。所以不要纠结“标准答案是什么”,而要把重心放在“我的模型能不能讲得通”。
第三,数学推导要写完整步骤。哪怕是简单的公式推导,我也建议把前提条件、每一步变形都写清楚。比如推导逻辑回归梯度时,经常看到有人直接写出 (X^T(y - \hat{y})) 的最终结果,看似正确,但如果你写清它是“先写出对数似然、求偏导、链式法则展开”的过程,得分点就会更完整。这一点在机器学习题和优化算法推导题里尤其重要。
第四,提前了解公司在该方向的业务布局。内推笔试虽然不直接考察你对阿里运筹优化业务的理解,但我强烈建议在笔试前认真了解目标公司在物流优化、推荐系统、定价策略等方面公开发布的技术文章或论文。这不仅有助于你理解笔试题中的业务场景,也能让你的建模选择更有针对性。比如如果你了解菜鸟网络的仓配一体化模式,再看到一句“多个仓库多个订单如何分配发货”的题目,就能更自然地想到用运输问题(transportation problem)来建模,而不是生硬地套一个指派问题。
第五,如果有余力,建议看一下基础的线性规划对偶理论和互补松弛条件(complementary slackness)。这块内容在笔试题里不常直接出现,但面试环节非常容易延伸到这里。面试官往往会拿着笔试题里的模型,追问“如果某个仓库容量变大了,最优成本会下降多少?”这类问题本质上就是在考对偶变量和影子价格。我当时因为准备得足够充分,面试环节在这个延伸领域拿到了不少分。
8. 关于运筹优化算法工程师——这个岗位的笔试只是入场券
最后想多说几句。笔试只是运筹优化算法工程师求职路上的一个环节,而且说实话是相对机械的一环。真正决定你和这个岗位匹配度的,是笔试之后的面试——面试官会详细盘问你的项目经历:你建过什么模型、变量是什么、约束怎么列、数据怎么处理、求解器卡住时怎么优化、线上效果怎么评估。
所以笔试准备过程,不能只看成“刷题过关”,而要借着梳理知识点的机会,把整个运筹优化技能树重新整理一遍:线性规划是地基,整数规划和组合优化是主战场,启发式算法是关键时刻的救命稻草,机器学习是输入参数的来源,工程能力是落地部署的保障。这五样东西缺一不可。如果笔试准备能帮你在心中建立起这样一张完整的知识地图,那即便内推因为名额问题没有进入下一轮,这份地图也会在你后续的学习和工作中持续产生价值。
对于正在准备类似岗位笔试的同学,我的核心建议可以概括成一句话:用建模视角去看每一道题,而不是用应试视角去背每一个答案。题目会变、年份会变、公司会变,但“把业务问题抽象成数学模型,再用合适的方法求解”这一核心能力,是永远不会过时的。祝各位顺利。