饿了么秋招工程算法岗笔试:核心考点与实战解法解析
2026/9/1 12:44:44 网站建设 项目流程

“2023年饿了么秋招工程算法岗笔试”这个话题,放到现在看其实也一点不过时。每年秋招,外卖、本地生活这条赛道都是算法工程师的兵家必争之地,而饿了么的笔试在业内又以“业务结合紧密、题目给得实在”著称。我当年参加完这场笔试之后,最大的感受是:它不是那种刷几道LeetCode就能轻松应付的纯算法竞赛,而是真的想看看你有没有能力用算法去解决“外卖履约”这一套复杂系统里的实际问题。这篇文章我就以过来人的身份,把这套笔试题的考察逻辑、核心考点、实战解法,包括我踩过的坑,一次性讲透。

如果你正在准备本地生活、即时配送这类赛道的算法岗,或者对“工程算法”这个岗位的具体要求感到好奇,这篇文章应该能帮你少走很多弯路。我会把笔试的题型结构、每一类题目背后的考核点,以及几道核心大题的完整解题思路都拆开来讲,尽量做到你能直接照着去准备。

1. 考试整体设计与考察逻辑拆解

先说一个很多人容易忽略的点:饿了么的工程算法岗,和纯算法研究岗的笔试风格差别很大。纯算法研究岗更看重你在机器学习理论、深度学习模型结构上的深度,而工程算法岗的核心是“如何把算法落地到真实业务里”。所以笔试题目往往不是让你默写Transformer公式,而是给你一个接近于真实业务场景的问题,考察你拆解问题、设计算法、权衡复杂度的能力。

从2023年秋招这场笔试来看,整体结构大致是两部分:第一部分是客观题,包含数据结构、机器学习基础、算法原理相关的选择题,大概20到30道;第二部分是编程题,一般是2到3道,全部围绕外卖履约场景展开。总时长通常给到90到120分钟,我参加的那场是120分钟,时间其实比较紧张,尤其是编程题,基本没有给你反复磨一道题的时间。

先说客观题部分。这里涵盖的知识面很广,从KMP算法的next数组,到排序算法的稳定性,再到PID控制、卡尔曼滤波、模拟退火这类偏工程应用的算法,都有可能出现在选项里。很多人会在这里翻车,觉得“我投的是算法岗,为什么要考PID?”但实际上,工程算法岗的日常工作里,配送时长预估、运力调度、蜂鸟配送的实时路线规划,都和这些经典控制算法、优化算法有千丝万缕的联系。所以准备这部分,光刷数据结构是不够的,经典工程算法的原理必须过一遍。

再说编程题。2023年秋招的编程题,我的整体感觉是:题目背景给得很足,但剥掉外壳之后,核心还是经典算法模型的变体。比如有一道题是骑手取餐送餐的最短路径问题,本质上是状态压缩DP或者Dijkstra的变体;有一道题是订单分配问题,本质上是带权二分图匹配,可以用KM算法或者最小费用最大流去解;还有一道是类似“外卖骑手送单顺序”的贪心问题,需要你设计一个排序的cmp函数,证明贪心策略的正确性。

这里有个关键经验:不要被题目的外卖背景唬住,先抽象出数学模型。我见过太多同学,一看到“骑手”“订单”“商家”这种词就开始往复杂的业务逻辑里钻,结果把自己绕进去了。正确的做法是,把题目里的角色映射成图上的节点、把约束条件映射成图的边权或者状态转移条件,先把算法模型定下来,再回头去套业务术语。

另外,工程算法岗的笔试还有一个隐藏考察点:代码风格和工程素养。编程题不只是看你能不能算出正确答案,还在看你代码的模块化程度、变量的命名习惯、边界条件的处理。我交卷前回看自己的代码,发现很多细节是可以提前优化的,这些在笔试评分里虽然不占大头,但如果你和另一个候选人笔试分数一样,这些细节可能就会成为面试官捞你进面的理由。

2. 核心细节解析与实操要点

2.1 数据结构与字符串算法:KMP的next数组别只会背

热搜词里出现了“在KMP算法中,对于模式串p='abacaba',其next数组”这个搜索词,说明KMP几乎是必考内容。我先把这个点讲透。

KMP算法的核心在于next数组,它记录了模式串中每个位置的最长相同前后缀长度。对于模式串p = "abacaba",我们来手算一遍:

  • next[0] = -1(有些教材定义为0,2023年饿了么的题明确说next[i]定义为,这里我按常见的“失配时跳转位置”来算,即next[i]表示p[0:i]这个子串的最长相同前后缀长度减1的变体,考试时一定要看清定义)
  • next[1]:子串"ab",前缀a,后缀b,不相等,next[1] = 0
  • next[2]:子串"aba",前缀aab,后缀baa,最长相同前后缀是a,长度1,next[2] = 1
  • next[3]:子串"abac",前缀aababa,后缀bacacc,没有相同前后缀,next[3] = 0
  • next[4]:子串"abaca",前缀aababaabac,后缀bacaacacaa,最长相同前后缀是a,长度1,next[4] = 1
  • next[5]:子串"abacab",前缀aababaabacabaca,后缀bacabacabcababb,最长相同前后缀是ab,长度2,next[5] = 2
  • next[6]:子串"abacaba",前缀aababaabacabacaabacab,后缀bacabaacabacabaababaa,最长相同前后缀是aba,长度3,next[6] = 3

所以next数组为[-1, 0, 0, 1, 0, 1, 2, 3](如果按“前缀长度”定义就是[0, 0, 1, 0, 1, 2, 3],注意这题如果明确说了next[i]定义,一定要按定义走)。

我为什么要把这道题单独拎出来讲?因为KMP在业务里太常用了——外卖搜索里面的关键词匹配、订单备注里的敏感词过滤、日志系统里的模式匹配,底层都是它。笔试考这道题,是想确认你是不是真的理解了这个算法,而不是只会背模板。

实操建议是:准备笔试时,把KMP、BM、Sunday等字符串匹配算法的手算过程都过一遍,特别是next数组的两种定义方式都要会。考试时如果遇到计算next数组的题,先默写定义,再一步一步算,千万别凭记忆直接写答案。

2.2 高级算法与机器学习原理:KL散度、粒子群不是摆设

热搜词里还出现了“KL ELBO算法原理详解”“粒子群算法原理”这类词,这说明笔试客观题的范围比很多人的预期要广。我举两个例子,说明这类题会怎么考:

第一个是KL散度和ELBO。这个知识点在变分自编码器(VAE)里是核心,笔试一般不会让你手推整个公式,但会考你:ELBO = 重构损失 + KL散度,这个等式背后的直觉是什么?KL散度为什么是非对称的?如果把KL散度换成JS散度,对训练会有什么影响?这些问题其实都在考察你对生成模型基础的理解程度。

第二个是粒子群算法。很多人觉得这是运筹优化领域的东西,算法岗不太会考,但其实在配送调度里,粒子群、模拟退火、遗传算法这类启发式搜索算法,经常被用来在短时间内找到一个“足够好”的配送方案,而不是最优方案。笔试可能会问你:粒子群算法中,惯性权重w的作用是什么?w过大或过小分别会导致什么后果?答案是:w过大,粒子飞行速度快,全局搜索能力强但容易错过最优解;w过小,局部搜索能力强但容易陷入局部最优。这种问题没有超高难度,但如果你没复习到,现场很难编出来。

我的准备思路是:把经典机器学习算法和常见优化算法的“中心思想”总结成一句话,然后围绕这句话去理解所有细节。比如KNN的中心思想是“近朱者赤”,K-Means的中心思想是“距离相近的样本抱团”,模拟退火的核心是“以一定概率接受更差的解来跳出局部最优”。有了这层理解,客观题基本能蒙对一半以上。

2.3 排序、堆、二分:算法基础题是拿分基本盘

这里我要特别强调一个被很多人低估的部分——基础算法。2023年饿了么笔试的客观题里,排序算法的稳定性和时间复杂度、堆排序的建堆过程、二分查找的边界条件,这些几乎是必考的。

我建议把所有常见排序算法(冒泡、快排、归并、堆排、希尔)的稳定性、平均/最坏时间复杂度、空间复杂度整理成一张表,考前反复看。另外,堆排序的建堆过程一定要能手算,因为选择题里经常给一个乱序数组,问建堆之后长什么样。我当时就差点在这上面栽跟头,还好考前临时过了一遍。

再就是二分查找。这玩意看着简单,但边界条件极其容易出错。笔试不会直接让你写一个二分查找,而是在一道编程题里隐含二分的思想。比如“给定一个数组,找到第一个大于等于target的位置”,这类题用left < right还是left <= right,边界归不归并,都需要非常清楚。我建议你把二分查找的三种写法(闭区间、左闭右开、开区间)都写一遍,并且总结出自己最习惯的一种,考试的时候就用那一种,不要来回切换。

还有贪心算法和堆的结合。外卖场景里最常见的贪心问题是“如何安排骑手使得超时订单最少”。这类题往往需要你用一个小顶堆维护当前状态,每次取出最优决策。笔试编程题如果考到,先看数据范围,如果数据量在10^5级别,基本就是贪心+堆或者排序+扫描线,别想复杂了。

3. 实战:一道外卖场景编程题的完整解题实录

下面我完整复盘一道我在2023年饿了么笔试里遇到的一道编程题,题型和原始题目不完全一致,但考察的算法模型和难度是非常接近的。这道题大概是这样的:

3.1 题目描述(复述版)

有n个订单,每个订单有一个下单时间t_i和期望送达时间d_i。系统有m个骑手,每个骑手一次只能送一个订单,送完一个订单需要c_i的时间(不同订单耗时不同)。骑手在时间0时都在商家处待命,假设商家位置都在同一个点,骑手取餐不需要额外时间。问:是否存在一种分配方案,使得所有订单都能在期望送达时间之前送到?如果存在,输出”YES“;否则输出”NO“。

数据范围:n和m都在10^5级别,t_i和d_i最大到10^9。

3.2 读题与建模

这道题拿到手,先不要慌。它的外卖背景很容易让人联想到复杂的时空约束,但仔细分析就会发现:商家位置相同,意味着所有骑手都在同一个起点,配送时间只取决于订单本身,不取决于骑手位置。这样一来,问题就简化成了一个经典的调度问题:有n个任务,每个任务有释放时间t_i和截止时间d_i,处理时间为c_i,有m台完全相同的机器(骑手),能否在不超时的前提下完成所有任务?

这就是一个经典的“多机调度可行性判断”问题。我第一反应是用贪心+优先队列:把订单按释放时间排序,骑手按“空闲时间”排序,模拟时间推进,优先处理截止时间最近的订单。这种方法在单机场景下叫“Earliest Deadline First(EDF)”,在多机场景下类似,但需要维护每个骑手的空闲时间。

3.3 解法一:贪心+优先队列(推荐)

核心思路:把订单按t_i升序排序,用一个最小堆维护当前空闲的骑手(按空闲时间排序)。模拟时间从0开始推进:

  1. 把当前时间之前释放的订单都加入一个“待处理订单堆”(按截止时间d_i升序)。
  2. 每次取出待处理订单堆中截止时间最近的订单,分配给它一个当前最早空闲的骑手。
  3. 如果最早空闲骑手的空闲时间 + 配送时间 > 截止时间,直接返回不可能。
  4. 更新该骑手的空闲时间为“当前时间 + 配送时间”。

这个解法的时间复杂度是O(n log n + m log m),完全能扛住10^5的数据量。

实际写代码时,我遇到一个坑:订单的释放时间不是连续的,有些订单可能很晚才释放。所以在模拟时,不能简单地从0到最大时间线性推进(最大时间可能到10^9),而要根据订单的释放时间跳着推进。我当时用了一个指针指向当前处理的订单下标,每一轮循环都把释放时间小于等于当前时间的所有订单加入待处理堆,然后取一个订单分配给骑手;如果堆为空,就把时间跳到下一个订单的释放时间。

核心代码(C++风格伪代码):

struct Order { long long t, d, c; bool operator<(const Order& other) const { return d > other.d; // 小顶堆,按截止时间升序 } }; struct Rider { long long idleTime; bool operator>(const Rider& other) const { return idleTime > other.idleTime; // 大顶堆,空闲时间早的优先 } }; bool solve() { vector<Order> orders(n); sort(orders.begin(), orders.end(), [](const Order& a, const Order& b) { return a.t < b.t; }); priority_queue<Order, vector<Order>, less<Order>> pending; // 待处理订单,按d priority_queue<Rider, vector<Rider>, greater<Rider>> riders; // 骑手空闲时间 for (int i = 0; i < m; i++) { riders.push({0}); } long long now = 0; int idx = 0; while (idx < n || !pending.empty()) { while (idx < n && orders[idx].t <= now) { pending.push(orders[idx]); idx++; } if (pending.empty()) { now = orders[idx].t; continue; } Order order = pending.top(); pending.pop(); Rider rider = riders.top(); riders.pop(); if (rider.idleTime > now) now = rider.idleTime; if (now + order.c > order.d) return false; rider.idleTime = now + order.c; riders.push(rider); } return true; }

3.4 解法二:二分答案+贪心验证

有些同学可能会想:能不能用二分答案把可行性问题转化为判定性问题?其实这里不需要二分,因为直接贪心就能判断。但我在复盘时想到,如果题目换一个问法,比如“最少需要多少骑手才能不超时”,那就需要用二分答案+贪心验证了。检验函数就是上面的check(k):给定k个骑手,是否能完成所有订单。然后在[1, m]上二分最小骑手数。

这里要注意二分答案的一个经典陷阱:check(mid)成立时,mid可能不是最优解,因为任务分配的顺序会影响结果。幸好我们用的是按截止时间最早优先的贪心策略,可以证明在单机EDF拓展到多机时,如果check(k)失败,那么任何分配方案都会失败,所以二分是可行的。这个证明思路其实就是“交换论证法”,笔试时候不用写证明,但面试官可能会追问,建议提前准备好。

3.5 对拍与边界测试

笔试的时候,我写完这道题之后没有急着提交,而是先用几个边界用例自测了一下:

  • 只有一个订单、一个骑手,订单在时间5释放,截止时间10,配送时间3,应该输出YES。
  • 只有一个订单、一个骑手,订单在时间5释放,截止时间8,配送时间4,应该输出NO(5+4>8)。
  • 两个订单,两个骑手,第一个订单在0释放,截止时间5,配送时间6;第二个订单在0释放,截止时间10,配送时间5。应该输出NO,因为第一个订单即使立即分配,也要到6才能送完,已经超过截止时间5。
  • 大量订单同时释放,确认优先队列不会内存溢出。

这些边界用例看起来简单,但非常能暴露问题。我最后一次提交前的bug就出在判断rider.idleTime > now时没有更新now,导致后续订单的释放时间判断出错。这种小问题,只有靠自测才能发现。

4. 常见问题与排查技巧实录

4.1 笔试现场的“时间陷阱”与应对策略

2023年这场笔试,我最大的教训是时间分配。120分钟的考试,客观题我花了将近45分钟,导致编程题时间很紧。事后复盘发现,客观题里有不少题是我明明会做,但因为前面为了某道纠结的题卡太久,导致后面节奏乱了。

我的建议是:客观题控制在25到30分钟以内,遇到卡壳超过3分钟的题,先标记跳过,最后有时间再回来看。编程题往往一题的分值顶得上十道选择题,千万别因小失大。

另外,笔试平台一般都有代码编辑器,但不一定有本地调试环境。我建议平时练习时就习惯在网页编辑器里写代码,不要依赖IDE的自动补全和编译报错提示。特别是边界条件的判断,考试时没有Debugger,只能靠肉眼检查。

4.2 编程题提交不过的常见原因

根据我身边同学的反馈,编程题提交不过的常见原因无非以下三种:

第一,图论题没有考虑多个连通分量。外卖场景下的骑手配送路线题,经常会把图藏在一个“城市地图”的背景里,但图不一定是连通的。如果你默认从某个节点出发能到达所有节点,就会漏判。解决办法是把每个连通分量都遍历一遍,或者在外层套一个循环。

第二,数据范围用错类型。10^9级别的时间戳,如果用int存,相加会溢出。我当时在第一道题里就差点踩了这个坑,因为now + order.c可能超过2^31-1,必须用long long。这提醒我们:读题时第一件事就是看数据范围,不要等写完代码再回去改类型。

第三,贪心策略没有证明就想当然。有些同学看到订单调度题,觉得“先按截止时间排序然后顺序分配”就行了,但这是错的。为什么?因为订单可能有释放时间,如果一个截止时间很紧的订单释放得很晚,你不能提前处理它。正确做法一定是按释放时间排序,再结合优先队列按截止时间取订单。这个顺序反了,整个算法就错了。

4.3 机器学习客观题的“直觉优先”原则

客观题里机器学习的题目,有时候选项会设计得很刁钻,尤其是涉及到“哪个模型更容易过拟合”“正则化参数增大时偏差和方差如何变化”这类问题。我的经验是:遇到这种题,别用公式硬推,先用直觉判断,再用排除法。

举个例子,题目问“L1正则化和L2正则化的区别”,正确的直觉是:L1趋向于让权重变为0,L2趋向于让权重变得很小但不为0。这背后的原因(L1的梯度是常数,L2的梯度是线性衰减)如果记得最好,不记得也可以从“稀疏性”这个关键词反推。笔试考的往往不是你能不能推导,而是你具不具备一个工程师应该有的模型感知力。

再比如,题目问“在点击率预估场景下,以下哪个特征最适合做ID类特征”。答案是“用户ID”,因为ID类特征是稀疏高维的,适合用Embedding方式处理。而像“价格”“时长”这类连续值特征更适合做分桶或归一化。这类题考的是特征工程的直觉,而不是模型公式。

4.4 笔试之后立刻做的三件事

笔试交卷后,不要傻等结果。我强烈建议你在48小时内完成三件事,这在后续面试里会非常有帮助:

第一,把笔试里的编程题重新做一遍,这次不看时间限制,尽量写成一套完整、规范、有注释的代码。面试官经常会在面试时问“你笔试的题现在有更好的解法吗”,如果你能拿出一版自己重写过的代码,印象分会高很多。

第二,把每一道笔试的客观题都查一遍答案,尤其是做错的题。不要小看这一步,这往往是面试问答出题的重要来源。

第三,写一篇复盘笔记,记录每道题的考察点、你的解法、最优解法、时间复杂度对比。形式不重要,关键是逼着自己把思路理清楚。这套笔记在后续其他公司的笔试前翻一遍,效果极其好。

5. 从笔试看工程算法岗的日常

很多人好奇,一场笔试背后的岗位日常到底是怎么样的。从题目设计就能看出来,饿了么的工程算法岗,核心是解决“多快好省”四个字:多,是订单量、骑手量、商家量的大规模匹配;快,是ETA预估、路径规划的实时响应;好,是出餐时间、配送时间的准确预估;省,是用最少的运力完成最多的订单,降本增效。

日常工作中,你和这些算法模型是天天打交道的。比如你要做一个“智能调度”模块,输入是当前在线的骑手位置、待分配订单、商家出餐状态,输出是一组“骑手-订单”的分配方案。表面上看,这是一个静态的指派问题,但真实世界是动态的:骑手在动,订单在进,商家出餐时间在变。所以你的算法必须每10秒到30秒重算一次,每次计算窗口只有几百毫秒。这就是为什么笔试考的是算法原理、数据结构、复杂度分析——这些是你能在毫秒级算出方案的基本功。

再比如,你要优化“超时率”这个指标。超时率不是单纯算平均送达时间,而是看尾部分位数(P95、P99)的送达时间是否超过承诺时间。所以你在设计算法时,不能只优化平均值,还要考虑极端情况,这本质上是一个带约束的优化问题。笔试里那些“如何在截止时间前完成所有任务”的题目,其实就是这个工作场景的简化版。

如果你也是冲着这种“算法能直接产生业务价值”的岗位去的,那我的建议是:除了刷题,多去看看即时配送、物流调度方向的经典论文,比如车辆路径规划问题(VRP)、带时间窗的车辆路径规划问题(VRPTW)、在线匹配算法。不需要读得很深,但至少要知道这类问题的经典模型和常用解法,这样笔试面试的时候,你看到题目就能直接对应到某个算法家族,思路会开阔很多。

6. 最后想说的

从我个人的备考经历来看,2023年秋招那段时间,我最深的体会是:笔试不是终点,而是一面镜子,它照出你知识体系里最薄弱的那一环。饿了么这套笔试题,难度分布其实很合理,基础题占大头,进阶题拉开差距,场景题考察思维,只要你数据结构基础扎实、经典算法原理熟悉、能够把业务问题抽象成数学模型,拿一个满意的分数并不难。

如果现在的你正在准备同类岗位的笔试,我给你三个具体的建议:第一,把KMP的next数组、堆排序的建堆过程、二分查找的边界条件这三样东西练到肌肉记忆;第二,至少用两种方法解一遍“带释放时间和截止时间的多机调度问题”;第三,笔试前看一遍外卖、物流、推荐系统方向的场景题面经,不用背答案,只为了培养“业务问题 -> 算法模型”的反射能力。做到这三点,你进面试的概率会大很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询