美团2023校招笔试第1场编程题全解析:题型分布与实战策略
2026/9/1 20:25:52 网站建设 项目流程

每年到了这个时间点,总有学弟学妹拿着“美团2023校招笔试第1场编程题”来问我:原题在哪、到底难不难、该从哪儿开始准备。我通常都先泼一盆冷水:原题不太建议原样去背,一方面有平台版权和防泄漏的问题,另一方面背题对下一场笔试帮助很有限。真正值钱的,是把这场笔试背后那套稳定的出题思路摸透。这篇文章就是想把这套思路一次说清楚,包括整体题型分布、典型题目的解题策略、现场时间分配,以及我实际踩过的一些坑。不管你是正在准备秋招的应届生,还是想了解大厂笔试风格的后端或算法方向同学,认真看完都能少走不少弯路。

1. 先把美团2023校招笔试第1场的大盘摸清楚

1.1 考试时长、题量与难度梯度

美团校招笔试一般是在牛客网这类在线评测平台进行,时间窗口大约90到120分钟,编程题通常是4道左右,偶尔会混入几道选择题,但真正决定能不能进面的是编程题。第1场属于整体批次里相对较早的场次,难度上不会有明显放水,也不会刻意劝退,基本遵循“前易后难”的排布:第1题送分,第2题看基本算法功底,第3题开始上强度,第4题用来区分头部选手。

我个人的体感是,前两道题如果代码功底正常,20到30分钟内应该能稳定拿下。真正拉开差距的是后两道,尤其是第4题,经常不是“不会做”,而是“想到了思路但没时间写完整”。这其实说明一个问题:笔试考的不只是算法能力,还顺带考了时间分配和心理素质。有些人卡在第3题大半个小时,最后第4题能拿部分分的解法也没来得及写,非常可惜。

对于绝对难度,我觉得对标的是LeetCode中等题偏上,偶尔出现接近困难的题目,但很少会出那种需要冷门数据结构的偏题怪题。美团尤其偏好用业务场景包装经典问题,这一点下面展开说。

1.2 考点地图:看似“业务题”,内核全是经典算法

美团笔试一个特别明显的特征,就是题目描述会说一大堆业务背景,比如配送、商家评分、满减券、外卖柜、订单调度。很多同学看到这种长题干就发怵,其实你只要把背景剥掉,剩下的全是熟面孔。

从第1场甚至整个批次来看,出镜率比较高的算法方向有这么几类:第一是字符串和模拟,通常放在第1题,考察对输入处理的细心程度;第二是排序加贪心,经常和区间、调度相关;第三是动态规划,尤其是背包问题变体,比如有数量限制的红包券、优惠券组合,本质上都是多重背包;第四是二分答案,多和“最大化最小值”“最小化最大值”挂钩;第五是图论和并查集,偶尔会在第3题出现,比如给一批商户关系判断是否连通。

这里有个关键的判断方法:如果题干里出现“最多能完成多少”“最少需要多少”“是否能恰好凑到”这类问法,几乎可以直接锁定贪心、动态规划、二分这几条路线。先建立这个考点敏感度,再谈解题速度才有意义。反过来,如果你在考场上还在纠结题目是不是要用什么高级数据结构,那你大概率是把复习方向带偏了。美团笔试很少考偏难怪的数据结构,它更看重你对经典算法的迁移能力,也就是把业务描述翻译成算法模型的速度。

2. 四道典型编程题的思路拆解与参考实现

先说明一下:下面这四道题是我根据美团校招笔试常见的出题风格改编的同考点模拟题,不是原题搬运。原题在平台上没法外传,但解题思路和代码结构是可以完全复现的。把这几道题的套路吃透,你再去碰同类题目,基本能无缝迁移。

2.1 第1题:短信模板解析,别被花括号吓到

题目背景大约是:给用户发通知短信,模板里用 {name} 这种占位符标记需要替换的字段,再给你一批键值对,要求把模板里所有能替换的占位符全替换掉,没提供内容的占位符原样保留。

这题放在第1题,本质上是考字符串处理和对哈希表的熟悉程度。最直接的思路是遍历模板,遇到 { 就往后找 },取出中间字段名,再到哈希表里查值。但如果对正则比较熟,一个 re.sub 加回调函数就能把代码写得很干净。

import re template = input().strip() n = int(input()) mapping = {} for _ in range(n): k, v = input().split() mapping[k] = v def do_replace(match): key = match.group(1) return mapping.get(key, match.group(0)) result = re.sub(r"\{(\w+)\}", do_replace, template) print(result)

这段代码里最关键的是 do_replace 中那句 mapping.get(key, match.group(0))。match.group(0) 是完整匹配到的占位符,比如 {name},把它作为默认值返回,就自动实现了“没有对应值就保留原样”的效果。如果你写成 mapping[key],遇到缺字段就会直接 KeyError,程序崩在第1题上,那才叫冤枉。

这类题还有个常见的变体,就是字段名可能重复出现多次,或者占位符里允许带数字。正则会帮你省掉很多手动扫描的麻烦,不过前提是你对 pattern 要心里有数。如果想稳妥一点,也可以不用正则,直接用字符串查找循环实现,虽然代码会长一点,但不容易出问题。笔试里没有要求代码必须优雅,能跑对才是第一位的。

2.2 第2题:骑手订单排序,贪心+反悔堆是比较稳的思路

这道题的典型设定是:一个骑手手上有若干订单,每个订单有完成耗时 t[i] 和预计截止时间 d[i],骑手按某个顺序逐单配送,开始后不能中断,问最多能按时完成多少单。

第一眼你可能想按截止时间排序然后直接模拟,这能拿到一定分数,但遇到“一个耗时巨大的订单把后面好几个订单全堵死”的情况,朴素贪心就不成立了。正确解法是“按截止时间排序 + 最大堆反悔”:先把所有订单按截止时间从小到大排序,逐个加入待办列表,如果当前总耗时超过了当前截止时间,就把已选订单里耗时最长的那一单踢出去。因为每踢掉一单,完成数量减一,但剩余总耗时下降最快,后面能接住的订单就越多。

import heapq n = int(input()) orders = [] for _ in range(n): t, d = map(int, input().split()) orders.append((d, t)) orders.sort() heap = [] cur_cost = 0 finished = 0 for d, t in orders: heapq.heappush(heap, -t) cur_cost += t finished += 1 if cur_cost > d: cur_cost += heapq.heappop(heap) finished -= 1 print(finished)

这里用最大堆是通过存 -t 实现的,堆顶是负数最小的那个,也就是耗时最大的订单。注意 if cur_cost > d 这个判断,很多第一次写的人会写成 while,实际上因为当前订单是加进堆里的,最多只需要反悔一次就能把总耗时压回范围以内,不需要循环。我之前就在这个细节上多写了个 while,不仅让代码变慢,还容易出边界问题。

为了验证这个思路,你可以构造一个反例:订单A耗时10截止10,订单B耗时1截止11,订单C耗时1截止12。最佳策略是先做B和C,A直接放弃,答案是2。朴素贪心按截止时间硬做会得到1,而反悔堆能正确得到2。这种反例在笔试时想不出来也没关系,但要记住:凡是“最多能完成多少个”还带截止时间的,优先往反悔堆方向靠。这个技巧在很多大厂笔试里都能复用。

2.3 第3题:红包凑数,多重背包的一次快速转化

第三题比较常见的形态是这样的:手上有 m 种红包券,每种有面额 val[i] 和数量 cnt[i],现在要凑出一个恰好等于 target 的金额,问最少用多少张券,凑不出来输出 -1。

这题看着像业务场景,其实就是一个多重背包求最小值。直接做法是把每种券按数量展开成单个物品,变成0-1背包,但 cnt 如果给到几千,展开后物品数量就会爆炸。需要先把多重背包做二进制拆分,把每种券拆成 1、2、4、8 这样 2 的幂次份,这样任意数量都能用 O(log cnt) 个物品表示。

target = int(input()) m = int(input()) items = [] for _ in range(m): val, cnt = map(int, input().split()) k = 1 while k <= cnt: items.append(val * k) cnt -= k k <<= 1 if cnt > 0: items.append(val * cnt) INF = 10 ** 9 dp = [INF] * (target + 1) dp[0] = 0 for w in items: for j in range(target, w - 1, -1): if dp[j - w] != INF: dp[j] = min(dp[j], dp[j - w] + 1) print(dp[target] if dp[target] != INF else -1)

为什么要二进制拆分而不是完全背包?因为完全背包意味着每种券数量无限,但题目里给了 cnt[i],数量是有限的,直接套完全背包会破坏数量限制,导致答案偏小。二进制拆分的原理可以理解成:任何小于等于 cnt 的正整数,都能用它拆分后的几个组合表示出来,因此这些子物品的0-1组合等价于原题的所有合法取法。

还有一个坑是 dp 数组初始化和状态转移的写法。如果不加 if dp[j - w] != INF 这个判断,初始的正无穷加上正无穷虽然不会溢出,但 min 的判断会显得多余。建议 INF 统一取 10^9,比最大可能答案大一个数量级就行。写完一定要自测两个边界:一个是 target 等于 0,此时一张券都不用,答案是 0;另一个是所有券加起来都不够 target,此时应该输出 -1,而不是 0。这两个边界在笔试里几乎必有一个,能守住就不会丢分。

2.4 第4题:外卖柜选址,一眼看出“最大化最小值”

最后一题比较经典的设定是:在一条直线上有 n 个可选的外卖柜位置坐标,要求从中选 k 个位置放置集中存放点,使得这 k 个点之间相邻距离的最小值尽可能大,问这个最大可能的最小距离是多少。

这类“最大化最小值”或者“最小化最大值”的问题,标准解法就是二分答案。你要先想清楚一件事:如果某个间距 x 可行,那么所有比 x 小的间距一定也可行,因为约束更松了。这种单调性正是二分的前提。于是问题变成:给定一个 x,能否在坐标序列中挑出至少 k 个点,使相邻选中点的间距都不小于 x。

def can_place(x): count = 1 last_pos = pos[0] for i in range(1, n): if pos[i] - last_pos >= x: count += 1 last_pos = pos[i] return count >= k n, k = map(int, input().split()) pos = list(map(int, input().split())) pos.sort() left, right = 1, pos[-1] - pos[0] answer = 1 while left <= right: mid = (left + right) // 2 if can_place(mid): answer = mid left = mid + 1 else: right = mid - 1 print(answer)

can_place 的写法是典型的线性扫描:从第一个点开始,能放就放,因为贪心选择最靠左的点永远不劣。这里边界条件很多同学容易写错,常见的有两处:第一,left 从 1 开始,因为距离肯定为正;第二,如果能满足,要记录 answer 而不是直接输出 mid,因为二分的最后一次可行值不一定就是 right,写成 answer 就没这个问题。

这个题时间复杂度是 O(n log(range)),range 是坐标范围。只要坐标值不超过 1e9,二分 30 次左右就能收敛,在笔试环境里非常快。如果笔试时间不够,只写出 can_place 并用小样例验证,也能拿到大部分分值,因为重点就是二分框架和贪心判断。我见过不少同学一上来就对坐标数组做双重循环,复杂度直接到 O(n^2),数据一大必然超时,非常可惜。

3. 笔试现场实操策略,能帮你在同样水平下多拿分

3.1 时间分配:不是所有题都要AC,先拿稳分

很多人有个错觉,觉得四道题都得 AC 才可能进面试。实际上大厂笔试,淘汰线通常看总分排名,一道题的部分分也有价值。我的建议是,拿到题先花 2 分钟把四道都扫一遍,判断哪些是送分题,哪些是思路题,哪些是硬骨头。第1题必须一次过,第2题尽量完整写出来,第3题如果动态规划写不顺,先写朴素版本拿部分分,第4题如果思路清晰就尽快写二分框架,不清晰就回去补第3题。

我自己见过不少同学在第一道字符串题上反复调试输入,浪费二十分钟,结果后面时间不够。你要是对自己的正则没把握,就不要硬用正则,老老实实遍历字符串,最多多写十行代码,但不容易出错。考场上“稳”比“秀”重要得多。另外,如果某道题的样例过了但提交只过了一部分,心态一定不能崩,先看有没有明显的边界问题,比如答案差 1、没处理空输入、数组下标从 0 还是 1 没对齐,这些小问题经常会导致大面积超时或错误。

3.2 输入输出与本地调试:把前20分钟花在刀刃上

笔试平台和本地运行环境有些差异,尤其是 Python 的 input() 在数据量大的时候会偏慢。我建议一开始就固定用 sys.stdin.buffer 读数据,这样既不会因为输入量大了超时,也能统一处理多行数据。本地调试时,不要把测试用例写死在代码里,我惯用的方式是在代码开头写一个读取模板,然后靠命令行输入重定向跑测试,比如 python3 solution.py < test.txt,这样可以快速切换多组用例。

关于输出,注意有没有“行末不要有多余空格”这类要求。虽然大部分平台不严格校验,但为了避免不必要的问题,最后统一用字符串拼接或列表 join 后一次性输出。代码里不要随便 print 调试信息,忘记删的话,轻则输出多余内容,重则直接判 WA。我见过最离谱的一次,是有人把调试用的 print 写在循环里,结果输出了一堆中间数组,平台直接判格式错误。

3.3 根据样例逆向反推题目意图

有时候题目描述特别绕,尤其是有业务背景的长题干,读三遍也没理清关系。这时候就别硬读了,把样例输入输出抄下来,用数据反推。比如样例里有 3 个订单、答案是 2,你可以手算一下,通常能猜出它是按截止时间排序还是按优先级排序。这个方法在考场上是合法的,也是老选手都会用的技巧。

数据本身就是一种文档。如果你看到样例中的最优解和你用某个常见算法跑出来的结果一致,那就大胆按这个算法写。但要留个心眼,样例通过不代表能过全部用例,能想清楚原理还是尽量想清楚,不然样例骗了你,后面会 debug 到崩溃。还有一个小技巧:如果样例里给了很特殊的边界值,比如 n=1、k=1、target=0,那多半意味着这些地方有坑,写代码时要格外注意。

4. 笔试常见问题、通过率卡壳与避坑经验

4.1 高频雷区排查速查表

我在帮别人复盘笔试时,发现大部分出错点高度重复。这里整理成一张速查表,方便你直接对照排查。

症状常见原因解决办法
输入读不全题目有多组用例,循环没写对用 while 按行处理,或根据题目给的组数循环
越界或运行时错误数组下标从 0 还是 1 没对齐统一采用 0-index,边界写成 <= 或 < len(arr)
答案差 1边界条件没想清楚针对 n=1、target=0、k=1 单独验证
超时复杂度太高,可能是 O(n^2)换二分、前缀和、堆,把数据规模再算一遍
输出格式错误多打了一个空格或换行用列表收集结果,join 后输出
本地通过平台WA数据类型用了 float,或没处理多组输入转 int,检查输入读取逻辑

有些错误一眼看不出,比如二分答案中 left 和 right 的初始值搞反,或者 dp 数组长度少一,都会导致通过率卡在 90% 左右。遇到这种情况别慌,通常不是思路错了,而是某个边界问题。拿随机小数据和自己想到的暴力解法对拍,比盯着代码干想快得多。

4.2 解决“本地通过、平台报错”的通用排查顺序

如果你确认自己的核心思路没问题,但平台就是报错,我的排查顺序是:先看输入读取是否覆盖所有情况,再看变量有没有从上一组用例里留下脏数据,再看类型是否用对,最后再看输出。90% 的“本地通过、平台报错”都出在这几层。具体来说,多组输入的时候,所有计数类变量都要在循环里重新初始化,这是个常年踩的坑。

另外,Python 的递归有深度限制,递归 DFS 在处理上万节点时会直接 RecursionError。笔试现场可没时间调整递归深度,所以图类问题尽量写成迭代版或并查集。我在准备过程中就吃过一次亏:明明思路完全正确,结果递归深度爆栈,提交直接判运行错误,当时连原因都没找到。后来养成了习惯,凡是图遍历第一优先写迭代栈,第二才考虑递归。

4.3 在线评测环境下容易被忽略的细节

在线评测环境还有一个特点:它跑的测试用例你看不见,所以代码里不能有任何依赖环境的东西,比如绝对路径、时间函数、随机数。随机数看起来和算法无关,但有些随机化算法在笔试里没必要用,一用就可能不稳定。所有算法都应该在确定性输入下得到确定性结果,这个理念最好从准备阶段就建立起来。

再补充一个很多人忽视的点:笔试页面通常有切屏监测,频繁切出去看资料会被标记。虽然不同平台策略不同,但建议把常用模板直接记在本地代码片段里,比如快速读入、二分框架、并查集模板,平均能帮你省下不少时间。准备阶段把这些模板背熟,考场上就不需要来回切换窗口。还有一点,正式提交前一定要把代码最后几行检查一遍,别让 print 调试信息混进去。

我记得自己第一次参加这类笔试时,前两道题写得很顺,结果第三题卡了四十分钟,最后只能用朴素写法的代码交上去,拿了部分分。现在回头看,真正帮我拿分的反而是那些踩坑经验:边界条件多验证、输入读取提前定好模板、每个复杂算法先想清楚单调性再写框架。希望这篇内容能帮你在考场上少走一点弯路。

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

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

立即咨询