8月的一个周六下午,我打开笔试链接,屏幕上是倒计时和四道编程题。美团2023秋招编程岗第一批笔试,算是每年校招里关注度最高的一场:投递人数多、题目风格典型、难度梯度明显,后面几批次的题目也经常和它相似。这篇文章我想把这次笔试的完整复盘写出来,包括四道题的解题思路、考场上的时间分配、容易踩的坑,以及从题目反推出来的备赛方向。无论你是准备投美团的技术岗,还是想拿大厂笔试练手,这份复盘应该都能给你一些实处的东西。
先交代一下背景:美团笔试用的是牛客/赛码这类在线评测平台,编程题部分大概四道,时间比较紧,总分按通过用例比例给分。也就是说,不是只有AC全部用例才得分,部分通过也有分。这个机制很关键,后面我会专门讲怎么利用它。
1. 笔试整体体验与题目结构分析
1.1 美团笔试的节奏与风格
美团笔试给我最直接的感受是:题面不长,但每一道都裹着一层业务场景。比如“小美有一些任务”“小明去游玩”之类的包装,本质还是算法题。这种风格对校招同学其实挺友好,因为读题压力不大,关键是把场景抽象成算法模型。
时间分配上,四道题大概给了100分钟左右。听起来很宽裕,但实际进入状态后你会发现,如果第三题卡住,第四题基本就没时间碰了。美团笔试的难度曲线通常有点“前缓后陡”:第一题是签到题,第二题是经典贪心或模拟,第三题开始上强度,第四题往往是动态规划或者状态压缩这类硬骨头。第一批的题目也是这样。
还有一个特点:美团的题对复杂度要求很明确。数据范围放在那里,基本告诉你该用什么算法。n在10^5级别就是O(n log n)或O(n),n在20以下就要考虑状压或者爆搜。很多人笔试翻车不是因为不会做,而是因为选错了算法,复杂度估错,跑大用例直接超时。
1.2 第一批四道题的考点分布
这里先给个总览表格,后面逐题细说:
| 题号 | 核心考点 | 难度预估 | 值得注意的点 |
|---|---|---|---|
| 第一题 | 字符串处理、贪心 | 简单 | 边界条件多,容易想复杂 |
| 第二题 | 区间调度、贪心排序 | 中等偏易 | 排序规则要想清楚 |
| 第三题 | 前缀和、哈希表 | 中等 | 数值范围大,不能用滑动窗口硬搞 |
| 第四题 | 状态压缩DP、图论 | 困难 | 转移顺序和初始化是难点 |
从考点分布能看出来,美团不考太偏门的东西,就是基础算法里的高频题型:贪心、前缀和、动态规划。但注意,基础不代表简单,它会在边界条件和数据范围上给你挖坑。比如第三题如果没注意到数组里可能有负数,很容易写出“看似正确”的滑动窗口,然后被特殊用例卡住。
2. 四道真题的逐题拆解
2.1 第一题:字符串贪心,开局送分题
这题我印象里是这样一个模型:给定一个只包含小写字母的字符串s,每次操作可以把任意一个字符改成任意小写字母,求最少操作多少次,能让修改后的字符串中任意相邻两个字符都不相同。
这种题只要想明白一个点就很简单:当你从左往右扫描时,如果发现s[i] == s[i-1],你只需要修改s[i],不需要回头修改s[i-1]。因为前一个位置已经和前前一个位置确认过不相同了,你改了它反而可能破坏前面的状态。修改s[i]之后,它和s[i-1]肯定不同,接下来只需要担心它和s[i+1]撞车,所以直接把扫描位置跳过s[i+1]就行。
C++参考写法:
#include <bits/stdc++.h> using namespace std; int main() { string s; cin >> s; int n = s.size(); int ans = 0; for (int i = 1; i < n; i++) { if (s[i] == s[i - 1]) { ans++; // 当前字符被修改后,和左右都不相同,跳过下一个字符 i++; } } cout << ans << endl; return 0; }这个解法的时间复杂度是O(n),空间O(1)。实测样例和手推都没问题。
给大家提个醒:这题最容易犯的错不是不会做,而是把简单问题复杂化。我见过有人去枚举改成哪个字母,甚至写DFS回溯,完全没必要。你只需要计数,不需要真的构造出修改后的字符串。另一个容易忽略的点是循环里跳指针的边界,比如字符串是"aaa",第一次发现s[1]==s[0]后i变成2,循环结束,答案1,正确。如果是"aaaa",第一次i变成2,此时s[2]==s[1](因为没真改),答案2,也对。所以这个跳过写法是安全的。
2.2 第二题:区间调度,经典贪心的变种
第二题讲的是“小明一天最多能完整看多少个节目”。抽象成区间模型:每个节目有开始时间l和结束时间r,小明可以选择任意节目,但不能同时看两个,问最多能看几个。
这个就是经典的最大不相交区间数量。贪心策略:按结束时间从小到大排序,然后依次选择“开始时间大于等于当前已经选中的最后一个区间结束时间”的区间。
为什么要按结束时间排,而不是按开始时间或者区间长度排?因为结束时间越早,留给后面的时间越多。按区间长度排是很多新手容易犯的错误,比如一个短区间横跨两个长区间中间,选它反而会挡住后面两个,得不偿失。
核心代码:
#include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; vector<pair<int, int>> seg(n); for (int i = 0; i < n; i++) { cin >> seg[i].second >> seg[i].first; // first存结束时间,方便排序 } sort(seg.begin(), seg.end()); int ans = 0, last_end = -1; for (auto &p : seg) { if (p.second >= last_end) { ans++; last_end = p.first; } } cout << ans << endl; return 0; }时间复杂度O(n log n),排序是瓶颈。
这道题在美团笔试里算中规中矩,但要小心输入数据里会不会有l == r的情况,以及区间是否允许“端点相接”。一般完整看节目,如果上一个节目在t结束,下一个节目从t开始,是可以无缝衔接的,所以判断条件是>=而不是>。
2.3 第三题:前缀和加哈希,一道容易踩坑的中等题
第三题是“给定一个数组,问有多少个连续子数组的元素和等于k”。题面可能包装成小美挑选连续几天的营业额,但模型就是这个。
看到连续子数组和等于k,第一反应是滑动窗口。但这里有个隐藏信息:数组元素可能有负数。一旦有负数,滑动窗口的单调性就不成立了,窗口左边界不能简单地收缩。所以这道题的正确姿势是前缀和加哈希表。
核心思想是:前缀和pre[i]表示前i个元素之和。子数组[j+1, i]的和等于pre[i] - pre[j]。如果pre[i] - pre[j] == k,那么pre[j] == pre[i] - k。所以我们遍历i的时候,只需要查一下之前出现过多少个前缀和等于pre[i] - k,然后累加进答案,再把当前的pre[i]计数加一。
C++参考代码:
#include <bits/stdc++.h> using namespace std; int main() { int n; long long k; cin >> n >> k; vector<long long> a(n); for (int i = 0; i < n; i++) cin >> a[i]; unordered_map<long long, long long> cnt; cnt[0] = 1; long long pre = 0, ans = 0; for (int i = 0; i < n; i++) { pre += a[i]; ans += cnt[pre - k]; cnt[pre]++; } cout << ans << endl; return 0; }注意两个地方:第一,pre和cnt的value必须用long long,因为n最大可以到10^5,数组元素也可能到10^9,前缀和很容易超过int范围。第二,cnt[0]=1这个初始化一定要有,否则漏掉从第一个元素开始的子数组。这是很多人丢分的地方。
这题想考察的其实是“能不能识别数据范围对算法选择的影响”。如果只看题目不看数据范围,很容易写出滑动窗口的假算法,样例能过,但一跑大数据就超时或者报错。笔试里这种坑特别多,读题时务必把数据范围圈出来看一遍。
2.4 第四题:状态压缩DP,压轴硬骨头
第四题是典型的压轴题,模型是旅行商问题(TSP)的变种:给定n个城市的坐标或距离矩阵,从0号城市出发,每个城市恰好访问一次,最后回到0号城市,求最短总路程。n大概是18左右。
n等于18这个数据范围是一个很强的提示:如果暴力枚举排列,18!根本没戏,所以必然要往状态压缩DP上想。状态压缩的核心是把“哪些城市已经访问过”这个集合用一个二进制数mask表示,mask的第i位为1表示第i个城市已经访问过。
定义dp[mask][i]表示当前已经访问过的城市集合为mask,最后到达的城市是i时的最短距离。转移时枚举下一个未访问的城市j,更新dp[mask | (1 << j)][j]。最终答案是min(dp[(1<<n)-1][i] + dist[i][0]),也就是访问完所有城市后,最后停在某个城市i,再回到0号城市。
参考代码:
#include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; vector<vector<long long>> dist(n, vector<long long>(n)); for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) cin >> dist[i][j]; int total = 1 << n; vector<vector<long long>> dp(total, vector<long long>(n, LLONG_MAX / 4)); dp[1][0] = 0; for (int mask = 1; mask < total; mask++) { for (int i = 0; i < n; i++) { if (!(mask & (1 << i)) || dp[mask][i] >= LLONG_MAX / 4) continue; for (int j = 0; j < n; j++) { if (mask & (1 << j)) continue; int next_mask = mask | (1 << j); dp[next_mask][j] = min(dp[next_mask][j], dp[mask][i] + dist[i][j]); } } } long long ans = LLONG_MAX / 4; int full = total - 1; for (int i = 1; i < n; i++) { ans = min(ans, dp[full][i] + dist[i][0]); } cout << ans << endl; return 0; }复杂度是O(2^n * n^2),n=18时大概是6千万次量级,C++完全能跑。需要注意初始化:dp[1][0]=0,因为初始在0号城市,mask只有第0位是1。其他dp值先设成一个很大的数,然后用min去更新。LLONG_MAX / 4防止后续加法溢出,这个小细节建议养成习惯。
状压DP在笔试里属于“会者不难,难者不会”的类型。如果之前没见过,现场很难推出来。我的建议是考前至少把经典的TSP、棋盘覆盖、子集枚举题目练一遍,对这种“n很小但别的做法都过不了”的题就会形成条件反射。
3. 考场实战策略与避坑记录
3.1 时间分配:先保底,再攻坚
四道题100分钟,我的策略是:前20分钟把第一题和第二题解决掉,这两题属于“必须拿满”的部分,大概占40分。第三题留30分钟,争取拿满或者拿大部分分。最后30分钟给第四题,如果做不出完整正解,就写暴力或部分分,能过多少算多少。这不是放弃,而是利用“按通过用例给分”的规则最大化分数。
很多人习惯从第一题做到第四题,在一道题上死磕到AC才肯走。这种习惯在大厂笔试里会吃大亏。美团笔试的时间设计本身就没打算让你四道全AC,拉开差距的往往是“谁能在有限时间里拿到更多部分的分数”。第四题写一个O(n!)的暴力DFS,n=10以内的用例能过,也能拿到不错的分数。别觉得暴力丢人,笔试里拿到分才是硬道理。
3.2 现场容易忽略的五个细节
第一,多组输入和EOF问题。美团笔试题有时候不告诉你有几组测试数据,用while(cin >> n)这种写法更安全。第二,long long。只要数据范围超过10^9,求和、距离、前缀和这些一律用long long。第三,输出格式。有的题要求空格分隔,有的要求换行,有的要求不能有多余空格,样例输出一定要看仔细。第四,本地调试和提交的差异。本地过了样例不代表能AC,要考虑极端输入,比如空字符串、n=1、最大值边界。第五,不要在代码里输出调试信息。我就见过有人本地调试完忘了删cerr,提交后输出一堆乱七八糟的东西,直接判错。
另一个值得说的是“看清楚题目给的变量名”。美团笔试喜欢把数组元素叫score、cost、price之类的业务词,看代码时容易和标准算法里的变量搞混。我习惯在草稿纸上先画出题目模型,再动手写代码,这个习惯能省很多反复读题的时间。
4. 从笔试反推美团技术偏好与备赛路线
4.1 美团笔试题背后的用人逻辑
美团技术岗的招聘量一直不小,笔试作为第一道筛选关卡,本质上不是为了考倒你,而是为了筛掉“基本功不扎实”的人。你看四道题的考点:字符串、贪心、前缀和、动态规划,全是大学数据结构和算法课里的内容,没有一道考偏题怪题。这说明美团看重的是基础算法的掌握程度和代码实现的熟练度,而不是你会不会某个冷门技巧。
还有一点,美团笔试的场景包装都在往业务上靠,比如节目安排、营业额统计、城市旅行。这传递出来的信号是:他们希望你具备“把业务问题抽象成算法模型”的能力。后端开发日常写业务代码,很多时候不是算法多难,而是能不能从一堆需求里找到核心逻辑。笔试其实就是在提前测试这个能力。
另外,美团后端的技术栈以Java为主,但笔试完全不限制语言,C++、Java、Python都可以。所以选语言的原则很简单:哪个熟练用哪个。别在考场上为了“试试新语言”而换语言,能用C++随手写出高复杂度代码,就用C++。
4.2 我的刷题建议与常见误区
如果目标是美团这类大厂的编程岗,刷题方向可以参考一个比例:高频算法专题占七成,包括贪心、二分、前缀和、链表、二叉树、动态规划;暴力回溯和状态压缩占两成;冷门数据结构占一成。LeetCode热题100加剑指offer的题量,覆盖美团笔试大部分考点是够用的。
很多同学刷题有个误区:一道题想了五分钟没思路就去看题解,看完觉得自己会了,过两天又忘。正确的做法是给自己限时,简单题15分钟,中等题30分钟,难题40分钟。没思路可以看题解,但看完必须自己重新写一遍,并且记录这道题的考点和解法关键词。我用这个方法刷了一个多月,笔试时最大的感受是“看到题面就能联想到它属于哪一类题”,后面顺着套路走就行。
现在AI编程工具确实很火,很多人拿它帮忙刷题、做题。但笔试现场只能靠你自己,所以基本功还是要一次次手敲代码练出来。你可以用AI辅助理解思路,但不要让它替代你思考。等到面试环节,手撕代码的时候更是这样,平时的积累骗不了人。
最后再说一个容易忽略的准备工作:提前半小时把电脑、网络、输入法都检查好,把常用语言的输入输出模板准备好。别小看这些琐事,我见过有人因为输入法没切换,写代码时中英文标点混用,编译卡了好几分钟。大厂笔试一年比一年卷,任何细节都可能影响最后的结果。希望这份复盘对你有帮助,祝笔试顺利。