尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

美团秋招编程题全解析:从链表到动态规划的高频考点与实战技巧

美团秋招编程题全解析:从链表到动态规划的高频考点与实战技巧 又到了秋招季整理旧电脑里的资料时翻到一份自己整理的“美团2019年秋招部分编程题汇总”当时为准备美团笔试我把市面上能收集到的题目都刷了一遍还逐题做了分类和复盘。今天拿出来再看发现其中很多题目至今依然是互联网公司笔试的高频原型对准备秋招的应届生、打算跳槽的工程师都有参考价值。美团编程题的整体风格是“基础为王、边界敏感”不追偏题怪题但会把经典问题包上一层业务外壳。这篇文章我会从考试结构、考点分类、真题思路、实战技巧和常见坑点几个方面展开尽量讲清楚每一类题背后的考察逻辑而不只是给一份答案清单。1. 考试结构与筛选逻辑1.1 笔试环节到底考什么美团秋招笔试一般是分批进行的不同批次、不同场次的题量会有差异。我当年遇到的是选择题加两道编程题总时长大概70到90分钟选择题覆盖计算机网络、操作系统、数据库、Java/C基础等编程题则是独立提交、自动判题。现在网上能找到的回忆版题目多数是编程题部分因为这部分最直观、最容易被记录和传播。选择题部分其实也要重视它决定了你能否进入面试环节的“简历池”。如果是非科班出身计算机基础比较薄弱建议重点复习TCP/IP、进程线程、数据库索引、哈希表冲突处理这些常考概念。美团选择题的难度不算极端但覆盖面广容易出现“看着都眼熟、选起来犹豫”的情况。我当时就因为一道数据库隔离级别的选择题犹豫太久导致留给编程题的时间变少。所以笔试前做几套模拟题训练选择题的手感是很必要的。编程题部分的意义更直接在线判题系统会自动运行你的代码用多组测试数据检查正确性。和面试手写代码不同笔试没有提示机会边界条件、输入格式、时间复杂度都得自己考虑到位。美团喜欢考的是“能看出来你会不会分析复杂度、能不能写清楚边界”的题目这一点从很多题目的数据范围就能感受到。1.2 看起来不难为什么淘汰率很高很多刷过美团笔试题目的人会有同样的困惑题目描述看起来并不复杂甚至有点像LeetCode上的中等题为什么通过率并不高我复盘下来原因有三个。第一时间压力。笔试现场不像平时刷题那样可以从容想半小时每道题从读题到提交可能只有20-30分钟。一旦在第一题上卡住心态波动会影响后面所有题。第二边界条件。有些题目的数据范围包含空数组、单个元素、全负数、重复值等特殊情况。测试用例会专门覆盖这些角落代码稍微疏忽就会挂掉。我见过很多人“本地运行没问题一提交就错”大多数时候不是思路错了而是边界没考虑全。第三业务包装导致读题成本高。美团很爱把算法题包装成外卖、配送、商家、用户的实际场景题目本身不难但需要先从一段比较长的描述中提取出核心问题。这其实也是在模拟真实工作中的需求理解过程。所以不要以为笔试只是考算法它同时考时间管理、阅读理解、心理素质和代码基本功。把这些维度都练到才算是真正准备好了。2. 核心考点分类与高频题型2.1 数据结构与基础算法绕不开的基本盘从2019年秋招的回忆题来看美团对基础数据结构的考察非常执着。链表、数组、栈、队列、哈希表这些“基本盘”几乎是每场笔试必出现的内容。常见考法包括链表反转、合并有序链表、判断链表是否有环、数组去重、滑动窗口、栈实现队列等。我整理了一份高频考点速查表刷题之前可以先对照这个表自查考点类别具体题型出现频率链表反转链表、环的入口、相交节点高数组/双指针两数之和、三数之和、有序数组合并高栈/队列括号匹配、单调栈、双栈实现队列中哈希表数组频次统计、缓存淘汰中滑动窗口无重复最长子串、窗口最大值中高这些题目单独拿出来都算不上难但笔试环境中很容易“一看就会一写就错”。以链表为例指针操作的顺序稍微写错就是死循环或者空指针建议平时就养成“所有链表题必须先画图再写代码”的习惯。画图不是浪费时间它能帮你把第一步、第二步、第三步的指针指向理清楚写完代码后再对着图检查一遍能发现很多逻辑漏洞。2.2 动态规划和贪心拉开差距的主战场如果只看基础数据结构很多同学都能拿到不错的分数。但真正决定你能不能进入下一轮面试的往往是动态规划和贪心这类“需要设计状态和转移”的题目。美团对动态规划的热爱体现在多个题目中最大子数组和、最长上升子序列、编辑距离、背包问题、买卖股票系列等都出现过类似原型。动态规划题型的核心不是背模板而是找到状态定义和转移方程。我在刷题时发现一个规律美团考的动态规划绝大多数是“一维DP”或者“简单二维DP”状态定义比较直观不会出特别复杂的状压DP或者树形DP。因此备考重心应该放在如何从题目描述中找到“前i个元素”或者“到第i个位置为止”的状态如何写出不重不漏的转移关系如何初始化边界值。贪心算法同样重要尤其在涉及区间调度、任务分配、最小花费的场景。贪心的难点是证明“局部最优能推出全局最优”笔试答题不需要严格证明但至少要想清楚反例不存在。如果推演两个测试用例后发现贪心策略自洽就大胆写下去。2.3 图论、字符串与其他区分度更高的题目图论和字符串题目在美团笔试中出现的频率不如前两类高但一旦出现就是拉开区分度的题目。图论的常客是BFS求最短路径和并查集比如在网格中找到从起点到终点最少走多少步存在障碍物、判断两个节点是否连通等。这类题套路固定属于“练过就不难”的类型。字符串题目则更灵活比如最长公共前缀、字符串匹配、回文子串等。2019年秋招的题目里有一类印象很深的题是给出一个字符串数组要求找出它们的最长公共前缀。这类题本身不难但需要注意字符串为空、数组为空、全部字符串完全相同等边界。还有一类是“前缀和”思想美团笔试出现过多次给定一个数组和若干次询问每次询问某段区间和。数据规模一上来每次遍历区间肯定超时前缀和数组可以做到O(1)查询。这种题目本质是在考察“空间换时间”的意识也是工作中写报表、做统计经常用到的思路。3. 真题思路全拆解附代码3.1 链表类题目快慢指针与环的入口链表几乎是每场笔试都不会缺席的考点。我印象里有一道题是“给定一个链表返回链表开始入环的第一个节点。如果链表无环则返回null”。这题在LeetCode上是142题属于很经典的题目美团把它搬到笔试里基本没有改场景。解题思路分两步第一步用快慢指针判断是否有环。慢指针每次走一步快指针每次走两步如果两者相遇说明存在环。第二步找环的入口相遇之后把一个指针移回链表头部另一个指针留在相遇点然后两个指针都每次走一步再次相遇的位置就是环的入口。class ListNode: def __init__(self, x): self.val x self.next None def detectCycle(head): slow head fast head while fast and fast.next: slow slow.next fast fast.next.next if slow fast: p head while p ! slow: p p.next slow slow.next return p return None这里需要解释一下第二步的原理。假设链表头部到环入口的距离是a环入口到相遇点的距离是b慢指针走了ab步快指针走了ab加上若干圈环的长度。因为快指针速度是慢指针的两倍所以快指针总步数等于慢指针步数的两倍。由此可以推导出从相遇点继续走到环入口的距离恰好等于从头部走到环入口的距离。因此把其中一个指针放回头部后同步移动再次相遇的位置就是入口。这道题给我们的启发是快慢指针不仅能判断环还能借助“路程关系”定位特殊节点。类似的题目还有“找到链表的倒数第k个节点”“找到链表的中间节点”原理都是通过控制两个指针的相对速度或相对起始位置来一次遍历解决问题。3.2 动态规划类题目最长上升子序列动态规划在美团笔试中出现的概率很高我挑一道非常典型的“最长上升子序列”LIS来拆解。题目描述通常是这样给一个无序整数数组找到其中最长上升子序列的长度。比如[10,9,2,5,3,7,101,18]最长上升子序列是[2,3,7,101]长度为4。第一次做这道题的同学很容易陷入“找连续上升子序列”的误区。注意题目说的是“子序列”不是“子数组”元素不要求连续。所以不能用滑动窗口必须用动态规划记录“以每个位置结尾时能得到的最长上升子序列长度”。def lengthOfLIS(nums): if not nums: return 0 dp [1] * len(nums) for i in range(len(nums)): for j in range(i): if nums[j] nums[i]: dp[i] max(dp[i], dp[j] 1) return max(dp)状态定义是dp[i]表示以nums[i]结尾的最长上升子序列长度。初始值都是1因为每个元素至少可以单独构成一个长度为1的子序列。对于每个i遍历所有ji如果nums[j] nums[i]说明nums[i]可以接在nums[j]后面那么dp[i] max(dp[i], dp[j] 1)。最后返回整个dp数组的最大值。这个解法的时间复杂度是O(n^2)如果数组长度达到10^5就会超时。进阶做法是“贪心二分”用tails数组维护当前长度下最小的上升子序列末尾元素可以把时间复杂度优化到O(n log n)。笔试时如果时间紧张先写出O(n^2)版本拿到部分分数再在剩余时间优化是比较稳妥的策略。这里想强调一个经验动态规划题不要着急写代码先花两三分钟把“状态定义”写清楚再写转移方程。状态定义一旦正确转移方程通常就顺理成章了。反之状态定义模糊会导致后期越写越乱。3.3 图论与搜索类题目网格中的最短路径美团笔试也出现过典型的BFS网格题比如“在一个n行m列的网格中0表示可通行1表示障碍物从左上角出发每次只能上下左右移动一步问到右下角最少需要多少步”。这类题目的本质是“无权图最短路径”BFS天然适合因为BFS第一次到达某个点时走的步数一定是最短步数。from collections import deque def minSteps(grid): m, n len(grid), len(grid[0]) if grid[0][0] 1 or grid[m-1][n-1] 1: return -1 visited [[False] * n for _ in range(m)] q deque([(0, 0, 0)]) visited[0][0] True directions [(1, 0), (-1, 0), (0, 1), (0, -1)] while q: r, c, step q.popleft() if r m - 1 and c n - 1: return step for dr, dc in directions: nr, nc r dr, c dc if 0 nr m and 0 nc n and not visited[nr][nc] and grid[nr][nc] 0: visited[nr][nc] True q.append((nr, nc, step 1)) return -1几个细节需要注意。第一入队时就把visited标记为True不能等到出队时再标记。否则同一个节点可能被多个方向重复加入队列既浪费空间又可能超时。第二边界检查要放在访问邻接节点时进行而不是在循环开头统一判断这样可以减少一次坐标转换的代码量。第三如果起点或终点本身是障碍物可以直接返回-1不用进入BFS。这种网格BFS题美团考过不止一次变体包括加入“传送门”“钥匙和锁”等特殊机制。应对的核心思路是把握住BFS的“逐层扩展”特性把所有状态信息都抽象成队列中的一个元素对象比如(x, y, 当前步数, 当前状态)。状态越多代码越复杂但本质上都是BFS模板的扩展。4. 实战技巧在线笔试的输入输出与调试4.1 输入输出处理读多行数据别翻车笔试和本地面试有一个很大的不同在线判题系统需要自己处理标准输入输出。很多同学刷LeetCode习惯了函数式调用不用关心输入格式结果到了笔试现场连一道简单的两数之和都因为input()用错而卡住。美团笔试题目的输入通常有两种格式单行输入和多行输入。单行输入直接用input().split()解析即可。多行输入尤其第一行是数据规模、后面跟若干行数据的情况建议直接用sys.stdin.read()一次性读取全部内容再按行或按空格拆解。这样可以避免循环调用input()时因为空行或末尾换行符导致读取错误。import sys def solve(): data sys.stdin.read().split() idx 0 n int(data[idx]) idx 1 arr list(map(int, data[idx:idx n])) idx n # 后续继续取数 print(arr) if __name__ __main__: solve()使用sys.stdin.read()的好处是无论输入多少行都能一次性拿到全部内容再按照自己的节奏解析。缺点是需要自己维护一个指针idx刚开始容易写乱。所以我的习惯是先把数据解析完打印出来确认无误后再写算法主逻辑。另外要注意输出格式。美团在线判题通常忽略行末多余空格但不要多打印调试信息。我见过有人忘记注释print调试语句导致输出结果混入了大量无关内容直接判错。建议在提交前做一次“clean check”把调试代码全部删掉或注释掉。4.2 复杂度估算1秒的硬限制在线判题的运行时间限制通常是1秒或2秒。1秒大约能跑多少操作呢以Python为例简单的循环操作大概能跑10^7到10^8次再高就会比较危险。所以拿到题目后要立刻做一个“数据规模分析”数据规模可接受的复杂度n ≤ 100O(n^3)甚至更高n ≤ 10^3O(n^2)n ≤ 10^5O(n log n)或O(n)n ≤ 10^6只能O(n)或接近O(n)如果n达到10^5你还在写双重循环哪怕代码逻辑完全正确超时的可能性也非常大。这时候要优先考虑二分、前缀和、双指针、哈希表、单调栈等能降低时间复杂度的技巧。我当年在笔试里吃过亏一道数组题数据范围写着n ≤ 10^5我第一反应是排序再遍历O(n log n)可以接受。但排序之后需要统计每个元素出现的次数我用了一个双重循环导致复杂度变成O(n^2)直接超时。后来改成用哈希表记录频次整个代码简洁且运行飞快。这个经验告诉我看到数据范围不要只判断“会不会超时”要具体到“最内层循环执行了多少次”然后再动手写。4.3 调试策略本地构造极端用例笔试现场的调试条件有限没有IDE的断点调试也不能反复提交几十次。我的调试策略是写完代码后先静态走查一遍关键逻辑再手动构造几个极端用例运行。极端用例包括空输入、最小输入比如只有一个元素、最大输入按数据范围上限构造、包含重复元素、包含全负数、包含最大整数值。比如写数组求和的题目一定要想想“全负数数组”情况下你的初始值是否正确写链表题目要想想“列表只有一个节点”“列表是空列表”时会不会空指针。如果本机运行结果不对不要急着乱改。先打印关键变量看每一步是否符合预期。尤其要注意循环里的边界条件比如while left right和while left right区别很大差一个等号可能就会死循环或漏掉元素。还有一个很多新手容易忽略的问题Python默认递归深度有限制通常是1000如果题目要求用递归解决树或图的遍历且数据规模较大建议直接改成显式栈或BFS。非要递归的话可以用sys.setrecursionlimit(10**6)适当提高限制但要注意递归过深仍有爆栈风险。5. 常见问题与考场避坑5.1 本地通过线上却不对这是笔试里最让人崩溃的情况。根据我的复盘本地能过、线上不过的原因通常逃不出这几类第一输入输出格式问题。比如题目要求输出保留两位小数你输出成整数或者多个测试用例之间要求空行分隔你没处理。第二全局变量污染。多次调用同一个函数时全局变量没有重置导致上一次的运算结果残留。第三数组越界或空指针在本地小数据上不触发但测试数据规模一大就暴露。第四Python中可变默认参数的陷阱比如def f(arr[])多次调用会累积数据。遇到这种情况最有效的排查方法是不要猜先构造和题目数据范围一样的用例逐条验证。如果自己构造的用例都能通过就检查输入输出的格式细节。我建议在提交前把你写的代码粘贴到本地Python环境里用python script.py input.txt的方式跑一下模拟在线判题的真实过程。5.2 超时了换思路别死磕常数代码逻辑正确但超时是笔试最常见的“隐性问题”。优化思路按优先级排列首先是换算法比如从O(n^2)降到O(n log n)其次是换数据结构比如用哈希表代替列表查找最后才是优化常数比如把for i in range(len(arr))改成for x in arr、用局部变量缓存重复计算。很多人超时后会陷入“为什么别人能过”的困惑。我的看法是如果时间复杂度的数量级不对再怎么优化Python代码的执行细节都救不回来。比如一个O(n^2)的算法在n10^4时内层循环要执行10^8次Python大概需要几秒到十几秒而限制只有1秒这时唯一的出路是降复杂度。应对超时的另一个策略是“部分得分”。在线判题通常会按通过的测试点给分。如果你只能想出O(n^2)暴力解法数据范围又很大至少可以加一些快速失败判断比如“如果数组长度大于某阈值就直接退出”但这是下策。笔试前多积累常见优化模板比如前缀和、差分数组、双指针、二分、单调栈才是治本的办法。5.3 面试官看重的不是代码是思考过程虽然笔试是机器判题但有些公司会调取笔试成绩和代码面试时围绕你写过的题进行追问。所以不要以为笔试通过就万事大吉你在代码里体现出的思路、注释、边界处理都可能成为面试环节的话题。我的建议是笔试代码也要保持清晰的变量命名关键步骤写一行注释不要在代码里堆砌没用的逻辑。尤其对于你“抱着碰运气心态蒙对的题”面试前一定要重新学会因为面试官看到你笔试通过后极大概率会追问“讲讲你这道题的思路”“为什么你的代码里这里要这么写”。如果答不上来反而会留下不好的印象。还有一点容易被忽视笔试结束后尽快把题目和你的解法记录下来。我当年刷美团题目时养成了一个习惯——每场笔试结束后趁记忆清晰把题目描述、自己的解法、最优解法、当时卡住的地方都写到一份文档里。这份文档后来成了我复习最重要的资料。很多网上流传的“美团2019年秋招部分编程题汇总”也是这么一棒一棒传下来的。提到这套题的价值我的个人体会是它不能只当“题库”来刷更要当成“能力体检”来用。如果哪一类题总是卡壳不要急着继续刷题先停下来补对应的基础知识点。数据结构、算法思想、复杂度分析这些底层能力才是真正能迁移到工作中、帮助解决实际问题的东西。刷题最终不是为了应付笔试而是通过这些题目训练自己拆解问题、设计方案的思维方式。带着这个心态去做题收获会比单纯背题多得多。
返回列表