十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

美团校招笔试真题复盘:字符串、数组与贪心队列全解析

美团校招笔试真题复盘:字符串、数组与贪心队列全解析 1. 为什么美团笔试总喜欢考这几类题先聊点实在的。2017年美团秋招的笔试编程题放到今天来看难度并不算离谱但它的出题风格非常典型基本奠定了后来几年大厂校招笔试的基本盘不是考偏题怪题而是考你基本功扎不扎实、边界条件考虑得全不全、代码能不能在限制时间内稳定跑出来。那年我印象最深的一个感受是题目量不大通常3到5道编程题但每一道题都会在某个地方卡你一下。你以为你在做“字符串处理”实际上它在考你哈希表和排序的稳定性你以为你在做“模拟题”实际上它在考你堆结构和状态设计。这种“表面简单、内里藏针”的风格正是美团笔试筛选候选人的核心逻辑。1.1 那一年笔试的基本盘我结合当时和后来几届同学的反馈整理一下美团2017秋招笔试编程题大致覆盖的题型范围。需要说明的是笔试题目属于回忆整理和原题可能存在细节出入但考察方向是确定的。题型分类典型考察点出现频率字符串处理去重、排序、子串匹配、字典序高数组与遍历双指针、前缀和、滑动窗口高动态规划背包、区间DP、路径计数中高贪心算法区间调度、排序取优中模拟题涉及堆、队列、状态机中图论基础最短路、并查集较低从这张表你可以看出美团笔试不太喜欢出那种一眼就能看出来要用某个高级算法的题。相反它更喜欢把你的基本功隐藏在一个看似普通的业务场景里。比如字符串处理它不会直接说“请你实现一个字典树”而是会给你一个“商家菜品名称去重排序”之类的包装。1.2 从出题意图反推复习方向我当时备考的时候有一个习惯拿到一套笔试真题先不看解法而是分析出题人到底想考察什么。美团这套题给我的感觉非常明确代码洁癖变量命名、注释、代码结构这些在笔试里虽然不直接打分但如果你代码写得乱调试时间会成倍增加最后导致时间不够用。边界条件意识空字符串、单元素数组、极端大的数值这些用例在美团笔试里几乎是必测的。复杂度预估能力很多题目在暴力的基础上增加了一个数据范围限制直接告诉你O(n^2)会超时逼你往O(n log n)或者O(n)上面想。多语言切换能力那个年代虽然大家都在用C但美团笔试系统允许你用Java、Python等语言提交。不少同学因为只会一门语言在某个知识点上卡住后没有备选方案整道题直接报废。所以与其说这是一篇题目解析不如说是一篇“如何应对美团式笔试”的全流程复盘。下面我会挑几道有代表性的题目展开讲完整的解题思路和代码实现。2. 字符串处理真题拆解隐藏的哈希表与排序问题2.1 题目描述与考察点按我的回忆有一道题大概是这样的给定一串由英文字母和数字组成的字符串要求去掉所有重复字符保留首次出现的字符然后按照字符的ASCII码从小到大排序输出。乍一看这道题很简单很多人的第一反应是遍历一遍找到首次出现的字符再排序完事了。但实际上这里非常容易踩坑。因为题目要求是“保留首次出现”而且在排序之后还要保持去重的逻辑不变。如果你先排序再去重得到的结果可能完全错误。举个例子输入字符串是cbacb正确流程先按“首次出现”去重得到cba再排序得到abc。错误流程先排序得到abccb再去重得到abc看起来结果一样但这个例子恰好没暴露问题。换个例子输入cabca正确流程首次出现去重得到cab排序得到abc。错误流程先排序得到aabcc去重得到abc结果一样。那什么时候会出问题题目的顺序是先“去重”再“排序”如果你直接调用语言自带的sort接口确实没什么问题。但如果你自己在排序过程中写了去重逻辑也就是边排序边比较相邻元素是否相同那你必须确定排序算法是稳定排序否则相同字符的相对顺序一旦改变去重结果会错。这个考点其实是在考察你有没有理解稳定排序和不稳定排序的区别。2.2 从暴力法到哈希优化这道题最朴素的做法是用一个布尔数组记录某个字符是否出现过然后遍历字符串把首次出现的字符收集起来最后排序输出。时间复杂度O(n k log k)其中n是字符串长度k是去重后的字符种类数空间复杂度O(k)。但如果你是2017年去参加笔试还有一个隐性限制笔试系统给出的字符串长度可能非常大比如10^6级别而且部分输入可能不允许你用额外的数组——因为它会给你一个“字符范围不固定”的描述让你不知道应该开多大的数组。这时候就要用到Python的set或者C的std::unordered_set配合std::vector来保存顺序信息。我个人推荐的做法是用哈希集合记录“已经遇到过的字符”用列表记录“去重后的字符顺序”最后统一排序。代码清晰逻辑简单不容易出错。2.3 实现代码与易错点def deduplicate_and_sort(s: str) - str: seen set() result [] for ch in s: if ch not in seen: seen.add(ch) result.append(ch) result.sort() return .join(result)这段代码可以在LeetCode或者任何在线编辑器里直接跑通。但笔试的时候真正容易出错的不是逻辑而是输入输出格式。美团笔试的输入输出通常是标准输入输出也就是说你要用sys.stdin.read()读取整行而不是写一个函数等系统调用。当年很多同学在本地IDE里测试通过提交到笔试系统里却报错原因就是没有写输入输出处理直接把函数定义了系统找不到入口。正确的提交示例import sys def solve(): data sys.stdin.read().strip() if not data: return seen set() result [] for ch in data: if ch not in seen: seen.add(ch) result.append(ch) result.sort() print(.join(result)) if __name__ __main__: solve()还有两个细节值得你留意读取内容可能包含换行符和空格直接用strip()会去掉首尾空白但如果你要处理字符串中包含空格的情况就得改成rstrip(\n)否则空格会被误删导致字符漏算。排序前忘记去重这道题如果直接先排序再去重在大部分测试用例下结果是一样的但对“保留首次出现”这个逻辑来说是不严谨的。如果面试官追加问一句“如果改成保留最后一次出现怎么办”你的解决方案是不是还成立3. 数组题深度解析最大连续子段和的三种解法与复杂度代价3.1 题目背景为什么这道题被反复拿出来考美团笔试里有一道几乎必出的经典题——最大连续子数组和。我为什么敢说它几乎是必出的因为这道题覆盖了三个核心能力暴力枚举的勇气、动态规划的理解、贪心优化Kadane算法的直觉。对校招生来说这是区分“会写代码”和“懂算法”的分水岭。题目大意是给定一个整数数组找出一个具有最大和的连续子数组至少包含一个元素返回其最大和。3.2 三种解法与复杂度对比解法时间复杂度空间复杂度核心思路适用场景暴力枚举O(n^3)O(1)枚举所有子数组并求和数据量极小前缀和优化O(n^2)O(n)前缀和数组快速求区间和小数据量动态规划/KadaneO(n)O(1)维护当前位置的最大子数组和实际笔试标准答案暴力解法我就不重复了估计你也能写出来。重点说说为什么前缀和优化在笔试中仍然不够用如果数据范围是10^4O(n^2)勉强能跑但到了10^5量级O(n^2)就稳稳超时了。2017年美团笔试的环境大概是什么水平当时普遍使用的是2秒到3秒的时间限制O(n^2)在10^5量级下必定超时。所以正确姿势是直接用Kadane算法。它的核心思想其实是一句话每到一个位置要么把当前元素加到之前的最优子数组后面要么从当前元素重新开始。因为如果之前的连续子数组和已经是负数那么它对于后续元素来说只会拖后腿不如直接丢弃。3.3 代码实现与测试用例def max_subarray_sum(nums): if not nums: return 0 current_max nums[0] global_max nums[0] for i in range(1, len(nums)): current_max max(nums[i], current_max nums[i]) global_max max(global_max, current_max) return global_max这里有一个非常关键的易错点current_max初始值不能是0而应该是nums[0]。为什么因为题目要求至少包含一个元素如果输入是[-1, -2, -3]你如果把初始值设为0那么current_max max(-1, 0 (-1)) -1这个逻辑没问题但global_max初始化为0的话最后返回0而正确答案应该是-1。血泪教训当年真的有人因为这个小小的初始化错误整道题0分。再给你一个完整的测试思路def test_max_subarray_sum(): assert max_subarray_sum([-2, 1, -3, 4, -1, 2, 1, -5, 4]) 6 # [4, -1, 2, 1] assert max_subarray_sum([1]) 1 assert max_subarray_sum([-1, -2, -3]) -1 assert max_subarray_sum([5, 4, -1, 7, 8]) 23 print(all tests passed)在笔试现场我建议你先在心里跑这几个用例再提交代码尤其是全负数用例和单元素用例。很多时候你以为的“小问题”恰恰是系统测试用例的重点。3.4 扩展思考如果题目改成“最大连续子数组乘积”怎么办这道题还有一个加强版美团在后续年份的笔试中直接考过最大连续子数组乘积。它的难点在于乘积存在负负得正的情况所以不仅要维护最大值还要维护最小值。虽然2017年这道题还没出现但你如果能顺手掌握变体对后续面试和笔试都有帮助。4. 实战考题有优先级的时间模拟问题考的不只是队列4.1 题目描述与出题意图有一道我印象很深的题背景是外卖订单调度。大意是有若干订单每个订单有下单时间和制作时长商家只有一个灶台单线程处理。订单有个优先级规则同一时刻只能处理一个订单如果新订单到达时当前订单还没做完需要根据某种规则决定先做谁。题目要求输出所有订单的完成时间。这道题本质上是单机任务调度问题用的是贪心优先队列。美团把这个业务场景包装成了外卖订单但它实际上考察的是你是否能读懂复杂场景描述并提取出核心模型你是否会使用优先队列堆处理动态到达的任务你是否能处理好“当前时间推进”和“事件处理”的逻辑这种题对只会刷LeetCode、不关心实际场景的候选人来说很容易在第一步“阅读理解”上就卡住。因为它不会直接告诉你“这是任务调度”而是用一大段话描述系统如何工作需要你自己抽象。4.2 堆加上状态机的设计思路我建议把它拆成三个部分来思考事件源订单到达是离散的事件需要按照时间顺序处理。工作台状态当前工作台是否空闲如果空闲就取下一个任务执行如果忙碌新到的任务进等待队列。等待队列按照题目的优先级规则排序通常用最小堆实现复杂度O(log n)完成插入和取出。核心逻辑是用一个current_time变量记录当前系统时间循环以下步骤把所有到达时间小于等于current_time的订单都加入堆中从堆中取出优先级最高的订单执行current_time前进到该订单完成的时间重复直到所有订单都被处理完4.3 完整实现与边界情况import heapq def order_schedule(arrival_times, process_times): n len(arrival_times) orders list(zip(arrival_times, process_times, range(n))) orders.sort(keylambda x: x[0]) heap [] result [0] * n current_time 0 i 0 while i n or heap: if not heap and current_time orders[i][0]: current_time orders[i][0] while i n and orders[i][0] current_time: # 这里的优先级规则假设为制作时长越短优先级越高 arrival, process, idx orders[i] heapq.heappush(heap, (process, arrival, idx)) i 1 process, arrival, idx heapq.heappop(heap) current_time process result[idx] current_time return result arrivals [0, 1, 3, 5] processes [5, 2, 3, 1] print(order_schedule(arrivals, processes))这里我把“优先级规则”简化成了“制作时长越短越优先”也就是Shortest Job First策略。但实际题目里优先级可能是“会员优先”“等待时间越长优先级越高”等规则组合。关键点在于堆的排序键要根据题目规则灵活调整有时候你需要存元组(优先级, 到达时间, 订单ID)来保证唯一顺序避免两个订单优先级相同时堆比较报错。边界情况有几个容易漏没有订单到达时时间如何推进如果当前工作台空闲且堆为空但还有订单没到达你需要把时间直接跳到下一个订单的到达时间而不是傻等。订单到达时间相同多个订单同时到达时先把所有满足条件的订单都加入堆再取一个执行顺序才不会乱。巨大时间戳如果你用int处理时间注意累加时可能溢出。Python的整数天然支持大数但C选手就得用long long。这道题我在实际笔试中没有全过最后有几个测试用例超时。后来复盘发现问题出在我把所有订单先按到达时间排序然后每次循环都从头扫描所有订单找“新到达”的订单导致整体复杂度变成O(n^2)。正确做法是维护一个i指针指向下一个还没处理的订单这样总体复杂度是O(n log n)。5. 笔试环境与代码提交的隐性规则5.1 多语言选型与输入输出格式2017年那会儿美团笔试系统支持C/C、Java、Python等语言。现在很多刷题网站已经支持更多语言了但在笔试现场选型要考虑的维度不同C运行速度快但代码量大调试慢。Java集合类丰富但输入输出样板代码多。Python代码量小适合快速实现逻辑但性能上限低如果在10^6数据量下用Python写O(n log n)算法勉强能过写O(n^2)必超时。一个实用的建议是在笔试开始前提前把某一种语言的输入输出模板写好并背下来。比如Python的import sys def solve(): # 读取一个整数 n int(sys.stdin.readline().strip()) # 整行读取多个整数 arr list(map(int, sys.stdin.readline().split())) # 输出结果 print(n) if __name__ __main__: solve()你也可以一次把所有数据都读进来然后按长度切分。对填空题和选择题较多的场次输入输出的差距体现不出来但编程题一多模板化就能节省10到15分钟的调试时间。5.2 时间复杂度预估习惯我见过太多同学在笔试结束之后抱怨“我思路是对的就是超时了”。超时问题的本质是复杂度预估不准。给你一个很粗糙但实用的估算标准假设1秒能跑大约10^7到10^8次简单操作C可以按上限估Python按下限估。如果你的算法复杂度是O(n^2)n是10^5那么在Python里大概率是10^10次操作必挂。但如果n是10^3O(n^2)随便跑。2017年美团笔试中的数据范围设计得非常“阴险”它通常会把暴力解法的复杂度压到刚好卡在超时边缘让你觉得“再优化一点点就能过”然后大量候选人在这个思路里耗尽时间。最优策略是看到题先估算复杂度如果发现暴力过不了立刻切换思路不要抱着侥幸心理去提交暴力解。5.3 部分AC的取舍策略笔试系统通常按测试用例比例给分意思是你哪怕只过了部分用例也能拿到一部分分数。这个机制很重要。面对一道完全没有思路的题我的策略是先写一个最简单的暴力解保证小数据量下能过。如果时间还有剩余再考虑优化某个环节把复杂度降一个量级。千万不要在一道题上死磕超过30分钟尤其是模拟题样例过了不表示边界没问题。举个例子如果题目要求O(n log n)但你只写出了O(n^2)的暴力解而数据范围里有一个“小数据子任务”那么暴力解至少能拿到这部分分数。笔试分数是一题一题累加的别因为“觉得丢人”连暴力分都不要。真实的场景里拿到60%的分数远好过0分。6. 从真题复盘到你的备战清单6.1 核心知识点优先级排序刷题这件事最怕平均用力。经过上面几道题的拆解你可以看出来美团笔试对知识点的偏好非常集中。如果要给你一个优先级排序我的建议是优先级知识点理由第一梯队字符串处理、数组遍历、哈希表几乎每场笔试都考代码量小容易出题第二梯队动态规划基础型、贪心中高难度是区分度的关键第三梯队堆、优先队列、排序算法通常和业务场景结合考验综合能力第四梯队图论、并查集、树偶尔出现但一旦出现往往在压轴题第五梯队高级数据结构线段树、树状数组2017年极少出现即便出现也有偏简易版本这里有个细节很多人第一梯队只练“LeetCode热题100”练得很熟但一到笔试题就开始懵。原因是你把题目抽象成“哈希表题”“双指针题”的时候只是在做模式匹配而美团喜欢把场景包装成业务描述导致模式匹配失效。所以备考时建议多找一些场景化描述比较重的题目来练习比如企业真实笔试题或者各大OJ里的“模拟算法”题。6.2 常见误区与心态调整第一个误区是过度追求难题。美团笔试里其实很少出现红黑树、后缀自动机这类进阶数据结构反而是基础题占了大多数。你把简单题做快、做准分数就上去了没必要为了炫耀去啃超纲内容。第二个误区是忽略全真模拟。我见过太多人刷题只在自己IDE里写函数不练在线笔试系统。真正笔试时第一场打字手速、读题速度、心理压力都会影响发挥。至少提前一周每天用牛客网或者赛码网等在线笔试平台模拟一场严格按照真实考试时间来自我约束非常有效。第三个误区是考后不复盘。很多人笔试结束只看分数不看重做。但实际上笔试题目是非常宝贵的复习资料哪怕你最后没有通过这场笔试把每一道题重新做一遍整理出“为什么我当时没想到这个解法”对你下一场的提升是巨大的。6.3 我个人的一点后话2017年那会儿我也和很多人一样疯狂刷题但真正到了笔试现场才发现决定成败的往往不是“会不会”某个算法而是“在有限时间内能不能稳定地把代码写出来”。那些平时看起来“基础到无聊”的知识点比如字符串去重、最大子数组和、输入输出模板恰恰是保住基本盘的关键。我现在的习惯是每次准备笔试前先自己列一个“必考基础清单”把每个知识点对应的模板代码默写一遍确保不依赖网络也能写出正确版本。比如字符串处理类题目我会提前写好deduplicate_and_sort这类函数虽然笔试时不能直接调用但默写一遍之后逻辑就已经内化了现场写起来行云流水。希望这篇复盘能帮你少走一些弯路。如果你正打算参加校招我建议你从今天开始每天用一道真题做“限时手写”训练不查资料不自动补全写完再对照答案分析差异。坚持两周你会上考场时发现很多题目不再是“见过”而是真正“会做”。
返回列表