ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

美团秋招编程笔试考点拆解:从二分答案到动态规划

美团秋招编程笔试考点拆解:从二分答案到动态规划 2023年8月中旬美团秋招第一批笔试如约开考。当时我们几个等通知的同学拉了个小群开考一小时后的画风基本是“选择题怎么有数字推理”“编程题第三题卡住了”“为什么我的BFS本地好好的粘贴上去就栈溢出”。等成绩的那几天群里有人陆续收到面试通知也有人发现自己挂在了一道看起来不难的签到题上。这篇分享不是官方真题解析笔试题有保密期网上流传的版本大多也是回忆。我根据自己的考试经历和同期同学的复盘把2023年秋招第一批编程岗笔试的整体结构、选择题范围、四道编程题的核心考点和解题思路以及考场上真正拉分的细节都整理了出来。目标是让准备秋招、春招的同学从“看过题”变成“看懂出题人的套路”。1. 收到笔试邮件以后赛制、平台与时间安排的细节1.1 第一批笔试的节奏到底怎么安排2023届秋招的节奏比往年更早投递简历之后大概一周到两周邮箱里就会收到笔试邀请。第一批笔试通常在8月中旬左右开考后续批次隔一周或两周一场整个秋招窗口会持续到10月甚至11月。当时我拿到的笔试通知是这样的邮件里明确写了考试时间、考试时长、使用的平台以及一个“考前模拟链接”。这个模拟链接千万别忽略我第一次美团笔试就因为在模拟环境里顺手点了“切换浏览器标签页”的灰色提示误以为开了防作弊后面做题时切窗口切得心惊胆战。其实只要不看那种明显违规的提醒正常做题不会触发任何东西。从第一批到后面几批笔试的题型基本相同但题目的具体内容和难度分布会有浮动。整体给人的感觉是它不追求考偏题怪题而是用有限的时间测出三件事——基础算法是否扎实、代码能否快速写对、在压力下能不能把该拿的分拿到。1.2 一套卷子里选择题和编程题是怎么拼起来的美团编程岗的笔试通常是选择题加编程题在同一套卷子里共用总时长。选择题大概20道左右涵盖逻辑行测、专业基础和语言特性编程题一般是4道难度递进。计分方式不复杂选择题按正确率给分编程题按通过的测试用例比例给分。编程题不是“全对才给分”过了一部分测试点就给一部分分这一点特别关键意味着哪怕没有AC暴力解法也能拿回一点分数。平台方面我印象中用的是牛客笔试系统进去之后能看到倒计时。编辑器功能比较朴素没有自动补全没有语法错误提示也没有代码格式化。习惯了IDE的同学第一次上手会有点难受所以提前用线上环境做一套模拟题很有必要。提示选择题和编程题共用倒计时别在选择题上较劲太久。后面章节我会细说时间分配的实操策略。2. 选择题不是走过场专业基础与行测逻辑的“五五开”2.1 专业选择题高频范围与典型例题美团笔试的选择题里专业基础的覆盖面大约是数据结构、操作系统、计算机网络、数据库、编程语言特性后端岗还会掺一些并发编程、异步编程、系统编程相关的内容。我印象比较深的几道死锁的四个必要条件。这个几乎是操作系统选择题的“钉子户”互斥、持有并等待、不可抢占、循环等待选项往往会把“循环等待”和“等待”混在一起看清再选。数据库索引失效的场景。比如在索引列上做函数运算会导致索引失效这类题考的不是能不能背出概念而是能不能判断具体SQL语句。HTTP状态码。302是临时重定向404是资源不存在500是服务端内部错误这几个高频状态码的语义必须记清楚。TCP与UDP的区别TCP的三次握手为什么需要三次。Java里堆、栈、方法区各放什么C的虚函数机制Python的GIL到底限制了什么——不同语言岗位会偏重各自的题目。刷题建议时间紧的话不用特意去背八股把操作系统、计算机网络、数据库这三门课的核心概念过一遍再刷几套“大厂笔试选择题题库”基本上能覆盖八成考点。2.2 行测类题目理工科考生最容易被偷袭的板块美团笔试的选择题里有一块让不少程序员的“脑子”转不过弯的内容就是行测逻辑类题目包括数字推理、图形推理、判断推理和言语理解。数字推理大概是这种画风给一串数“1, 2, 6, 24, 120”让你选下一个。答案是720规律是后一个是前一个乘以递增的自然数。图形推理会把一堆图形编成序列让你找规律考的是旋转、对称、封闭空间数量这类特征。判断推理则常见“甲乙丙丁四人只有一人说真话”之类。理工科同学经常在数字推理上丢分不是因为不会算而是因为“没有手感”。这类题特别吃熟练度考前花两三天每天刷20道数字推理和图形推理手感和速度能提升得非常快。我个人的策略是行测题不纠结超过一分半钟没思路就随便选一个把时间留给后面的专业题和编程题。3. 编程题考点地图美团偏爱这四类基础算法3.1 考点和业务的关系配送、调度、地图背后的算法原型美团笔试的编程题向来喜欢用小美、小团当主角把实际业务场景抽象成算法题。外卖配送需要路径规划和任务调度商家推荐需要动态规划与排序地图和POI搜索需要二分和连通块相关技巧。所以笔试高频考点集中在四类二分答案、图与连通块、动态规划、贪心加优先队列。很多同学喜欢刷偏难怪题觉得线段树、后缀数组、网络流这些才够“硬核”。但美团这种体量的公司做校招笔试核心目的是筛选“基础扎实、代码稳定”的候选人而不是选拔竞赛选手。四道编程题里通常只有最后一道有一定思维难度前三道只要把基础算法练到位完全能拿下。3.2 怎么快速识别一道题属于哪个考点考场上没有时间让你慢慢试识别考点是解题的第一步。我总结了一套快速判断的方法看到“最大值最小”“最小值最大”“在X次操作内能否完成”基本是二分答案先写check函数再套二分模板。看到网格图、无向图、连通区域、岛屿数量基本是DFS、BFS或并查集。看到“相邻不能选”“求最大收益”“求方案数”这类约束基本是动态规划。看到“最多能完成几个任务”“最少需要几台设备”优先考虑贪心而且大概率要配合优先队列做“悔棋”操作。这套识别体系帮我在后面几次笔试里省下了很多时间也让我稳定地保住了前两道编程题的分数。4. 四道回忆版编程题从读题到AC的完整拆解以下四道题是根据考情记忆还原的等价题不是官方原题但数据范围、核心思路和坑位基本一致。我会把每道题从题目描述、思路推导到完整代码和易错点全部过一遍。4.1 第一题 数组上限最小化二分答案的签到题题目大意是给定一个长度为n的正整数数组每次操作可以把任意一个元素减1操作次数上限是K。问经过至多K次操作后数组中最大值的最小可能值是多少。数据范围大约是n到1e5a[i]到1e9K到1e14。看到“最大值的最小值”第一反应就应该是二分答案。我们来推一下单调性如果让最终最大值不超过x那么所有大于x的元素都要被减到x需要的总操作次数是sum(max(0, a[i]-x))。x越大需要的操作次数越少所以“能否在K次内完成”这个判断是单调的——满足二分条件。我习惯用左闭右闭的二分写法check函数里注意一点cnt累加时一旦超过K就提前返回false防止long long溢出是一方面也能省一点时间。初始左边界用0右边界用数组最大值答案一定在这个区间内。#include bits/stdc.h using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; long long k; cin n k; vectorlong long a(n); long long maxv 0; for (int i 0; i n; i) { cin a[i]; maxv max(maxv, a[i]); } auto check [](long long limit) - bool { long long cnt 0; for (long long v : a) { if (v limit) { cnt v - limit; if (cnt k) return false; } } return cnt k; }; long long l 0, r maxv, ans maxv; while (l r) { long long mid (l r) / 2; if (check(mid)) { ans mid; r mid - 1; } else { l mid 1; } } cout ans \n; return 0; }时间复杂度O(n log M)M是数组最大值完全能扛住。这题真正的失分点不是思路而是类型K和cnt不开long long边界一测就炸。考场上我见过不止一个同学因为int溢出在一个签到题上卡了半小时。4.2 第二题 网格连通块DFS、BFS与栈溢出风险原题版本大概是给定一个n乘m的地图每个格子的值要么是0要么是11表示可通行。求由1组成的四连通块数量以及其中最大的连通块大小。n和m都可能到1000也就是格子数最多100万。这道题最直接的解法是DFS、BFS、并查集三选一。DFS写法最简单但递归深度在100万格子的极端情况下会让C调用栈吃不消我在本地测试时还正常一放到在线评测环境直接栈溢出。所以实际考试中我建议用BFS用一个队列逐层扩展同时用二维bool数组记录是否访问过。每次遇到一个未访问的‘1’格子就从这个格子出发做一次BFS把整个连通块标记完计数加一同时统计这个块的大小。#include bits/stdc.h using namespace std; int n, m; int dx[4] {0, 0, 1, -1}; int dy[4] {1, -1, 0, 0}; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin n m; vectorstring grid(n); for (int i 0; i n; i) cin grid[i]; vectorvectorbool vis(n, vectorbool(m, false)); int blocks 0, maxSize 0; for (int i 0; i n; i) { for (int j 0; j m; j) { if (grid[i][j] 1 !vis[i][j]) { blocks; int cnt 0; queuepairint, int q; q.push({i, j}); vis[i][j] true; while (!q.empty()) { auto [x, y] q.front(); q.pop(); cnt; for (int dir 0; dir 4; dir) { int nx x dx[dir]; int ny y dy[dir]; if (nx 0 nx n ny 0 ny m grid[nx][ny] 1 !vis[nx][ny]) { vis[nx][ny] true; q.push({nx, ny}); } } } maxSize max(maxSize, cnt); } } } cout blocks maxSize \n; return 0; }时间复杂度和空间复杂度都是O(n*m)。这题的核心提醒是100万级别格子数的DFS要谨慎优先BFS另外memset一个100万大小的数组每次清空也可能超时用vector动态初始化最稳妥。美团把这道题放在第二题的位置难度不大主要看基本功扎不扎实。4.3 第三题 环形房屋偷盗DP的边界细节题目改写成“小美有一圈店铺每家店有一个收益值不能同时偷相邻两家店问最大收益。”这就是LeetCode打家劫舍的环形版本也是动态规划里的经典题型。美团会把场景换成“选择任务”“安排路线”但内核完全一样。普通线性版的转移方程是dp[i] max(dp[i-1], dp[i-2] a[i])。意思是当前店铺不偷那结果继承前一家当前店铺偷那前一家不能偷结果等于前前家的结果加上这家收益。环形怎么处理关键在于“第一家”和“最后一家”不能同时被选中。所以把问题拆成两个线性子问题不偷第一家在第二家到最后一家的范围内做线性DP不偷最后一家在第一家到倒数第二家的范围内做线性DP。取两者最大值。代码上我习惯用滚动数组省空间并且用一个左闭右开的区间参数来计算#include bits/stdc.h using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin n; vectorint a(n); for (int i 0; i n; i) cin a[i]; if (n 1) { cout a[0] \n; return 0; } auto rob [](int l, int r) - int { int prev2 0, prev1 0; for (int i l; i r; i) { int cur max(prev1, prev2 a[i]); prev2 prev1; prev1 cur; } return prev1; }; cout max(rob(0, n - 1), rob(1, n)) \n; return 0; }这里rob(0, n-1)计算的是不取最后一个元素遍历下标0到n-2rob(1, n)计算的是不取第一个元素遍历下标1到n-1。时间复杂度O(n)空间复杂度O(1)。这题真正的坑是n等于1时的特判。很多同学把线性版代码直接套到环形版上漏掉了n1的边界刚好被测试用例卡住。提交前第一件事永远是看数据范围、想边界情况。4.4 第四题 最多可完成任务数贪心优先队列的区分度题最后一道题通常是区分度题。题目大致是有n个任务每个任务有一个执行耗时和一个截止时间你可以按任意顺序执行最多能完成多少个任务。任务一旦开始必须完整执行完不能中断。如果只有一个执行者任务是串行的从时间0开始。这题能直接想到贪心但关键是贪心策略怎么定。正确做法是先把所有任务按截止时间从小到大排序然后依次“尝试”加入任务同时用一个最大堆维护已选任务的耗时。每加入一个任务如果当前总耗时超过了这个任务的截止时间就从堆里弹出耗时最大的任务相当于“后悔”之前选择了一个耗时过大的任务把它踢出去给后面的任务腾空间。为什么排序按截止时间而不是按耗时因为截止时间决定了紧急程度先处理截止早的任务才能保证“后悔”机制正确只有当前任务的截止时间比之前所有已选任务都更晚或更早这个弹出策略才能始终保留对全局最优解的近似。#include bits/stdc.h using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin n; vectorpairlong long, long long tasks(n); for (int i 0; i n; i) { long long t, d; cin t d; tasks[i] {d, t}; // first按截止时间排序 } sort(tasks.begin(), tasks.end()); priority_queuelong long pq; // 默认大根堆存耗时 long long cur 0; for (auto [d, t] : tasks) { pq.push(t); cur t; if (cur d) { cur - pq.top(); pq.pop(); } } cout (int)pq.size() \n; return 0; }时间复杂度O(n log n)主要是排序和堆操作的消耗。这题的“后悔”贪心是很多同学在考场上想不明白的点为什么我还没决定要不要这个任务就直接先把它加进去再弹出最大的这个“先假设选它再修正”的思路是贪心算法里特别重要的一种形态。它保证了我始终在“当前已考虑的任务集合”内保留一个总耗时最小且数量最多的组合。每次弹出耗时最大的任务不代表放弃当前这个任务而是放弃集合里最不值得保留的那一个让整体完成数量最大化。这道题我也见过一些同学用“按截止时间排完序后直接数有多少个能完成”的错误做法——在截止时间分布不均匀时这个贪心很容易被反例打脸。考场上看清数据范围再动手这类题想清楚“堆里存什么、什么时候弹出”再写代码会比边写边想稳得多。5. ACM模式输入输出思路再对也会被读入坑掉5.1 读入速度与在线编辑器的适配问题美团笔试是ACM模式也就是自己处理输入输出平台只负责给你喂数据、测答案。这和力扣那种“函数已经帮你把参数摆好了”的写法完全不同读入写错代码再对也是零分。在线编辑器的自动补全和本地IDE没法比所以我当时是先在本地IDE里把代码写好再粘贴过去。本地跑通样例之后还要额外测几组边界数据再提交。如果编辑器不支持自定义快捷键至少把读入模板提前背下来。C建议统一用ios::sync_with_stdio(false)和cin.tie(nullptr)加速Java写一个快读模板Python则用sys.stdin.buffer.readline而不是input()。特别是Python在大数据量下input()的性能差距非常明显。5.2 多组数据、栈溢出与整数溢出三个高频翻车点第一个翻车点是多组数据。有些题说“多组输入读到文件末尾”要写成while(cin n)或while(scanf(%d, n) ! EOF)的循环结构。美团笔试不常考多组输入但真碰到的时候很多人会忘了这个循环导致只处理了第一组。第二个翻车点是栈溢出。100万级别的二维数组递归DFS本地有的编译器能过在线环境不一定能过。BFS优先于递归DFS动态规划优先于记忆化搜索这是我在考场上总结出来的经验。第三个翻车点是整数溢出。题目数据范围到1e9甚至1e14的时候int几乎是必炸的。二分答案里的cnt、任务调度里的cur都要用long long。还有二维数组索引用n*m算下标时也要小心中间结果超int范围。另外一个细节输出别多加空格和多余换行。在线判题对格式比较敏感虽然很多评测机允许尾随空格但没必要冒这个险。调试的print语句提交前一定记得删干净。6. 考场上的做题顺序与抢分策略6.1 前5分钟扫题定顺序难度递进不代表必须按顺序做很多同学打开卷子就从第一题做到第四题这个习惯在美团笔试里可能会吃亏。四道编程题的难度并不严格按题号递增有时候第三题只是个普通DP第四题才是思维题有时候第二题反而需要处理一个隐蔽边界。我的做法是打开卷子后先花3到5分钟把四道题全部读一遍只读题意和数据范围不深入思考。然后标记出“一眼就知道怎么做”的签到题优先写接着做“有思路但细节多”的题最后集中时间怼最难的题。这样做的原因是笔试主要按通过测试用例比例给分先拿下两题满分比在一道难题上死磕两小时划算得多。选择题部分同样适用这个原则行测题超过一分半钟没有进展直接凭第一感觉选一个把时间留给后面更值钱的编程题。6.2 暴力分、边界分与“留空必零分”编程题如果实在想不出正解也要把暴力解法写上。数据范围小的时候O(n^2)枚举就能过一部分测试点拿到一部分分数。比如连通块题如果不会并查集写一个三重循环暴力遍历也能在小数据上拿到几十分。还有一类“边界分”有些题的答案在特定情况下是固定值比如n1时代入唯一解或者输不通时输出一个特定值。有思路的边界先写上能捞一点是一点。最忌讳的是碰到难题直接空着不写。空提交一定零分写一个暴力至少还有赢面。我当时的一个原则是每道题至少提交一个能编译通过的版本。哪怕只是读入数据然后输出示例答案也比交白卷强。7. 考完以后复盘、结果查询与面试衔接7.1 用错题本做一场有价值的复盘笔试结束后别急着放松趁记忆还热乎把四道编程题的关键点记录下来。我当时建了一个表格每行一道题列分别是题目所属考点、我的解法、卡住的原因、正解思路、这套解法还能迁移到哪些题。复盘的价值不在于“哦我会做了”而在于找出知识盲区。比如我做那道环形DP时漏了n1的特判复盘时就把“环形DP要拆区间”“所有DP先想边界”两条经验记下来。下一次笔试如果再遇到类似题这些记录能直接救命。7.2 笔试和面试之间的缓冲准备美团笔试出结果一般在一周左右通过后会在官网更新状态并发放面试邀请。笔试通知和面试之间通常隔几天到一周这段时间不要完全放掉算法题。笔试里的二分、连通块、DP、贪心基本就是面试手撕题的题池。我的建议是每天保持两到三道高频题的频率保持手感。如果笔试有哪道题没做出来优先把那道题的考点吃透面试中大概率还会以其他形式出现。另外现在很多同学备考时会借助AI编程助手来辅助刷题比如Cursor这类工具写题解、做代码解释确实方便。但我想提醒一句笔试考场上没有AI帮忙自己能不能独立推导出思路才是关键。所以平时可以拿AI当陪练但刷题时最好先自己动手写一版再让工具检查和优化代码。最后说一个我自己的小习惯正式笔试前我会把读入模板、常用头文件、快读代码在本地IDE里提前敲好开考后先花一分钟把这些适配到在线编辑器。这能帮你省下至少五分钟的“热身”时间让思路更快进入状态。笔试说到底考的是在有限时间内稳定输出那些刷题无数却总在细节上翻车的人才是最遗憾的。祝看到这里的朋友都能顺利进入面试。
返回列表