美团2013笔试题精讲:二分、链表、动态规划与系统设计

美团2013笔试题精讲:二分、链表、动态规划与系统设计 1. 从2013年美团笔试卷说起为什么老题仍然值得做2013年的美团正处于团购大战最激烈的阶段。当时的地推团队遍布全国技术团队却在快速扩张中笔试题目带着鲜明的“算法优先、工程落地”风格。我最近重新翻出这份老试卷认真做了一遍发现它的含金量比很多培训机构出的模拟题高出不少——不考偏题怪题全是在真实业务中会遇到的算法模型二分、链表操作、动态规划、系统设计思考。这份试卷适合谁两类人最值得刷一是准备大厂研发岗面试的应届生和跳槽者尤其是美团、京东、滴滴这类以O2O和交易系统为核心业务的公司二是工作几年后发现算法基础生疏、想系统回炉的工程师。2013年的题目没有现在面试中那么多“八股文”式的框架题它考的是纯粹的编码能力、边界思维和设计取舍这些能力直到今天依然是区分普通码农和靠谱工程师的核心标尺。我自己的感受是这份试卷最大的价值在于“去伪存真”。现在的面试题越来越卷动不动就是红黑树手写、LRU 多线程版本但真到业务里大部分人每天都在写 CRUD 和接口调用。美团2013年的题告诉你大厂真正想招的人不是背题机器而是能快速把模糊需求转化为可运行代码的人。2. 题型全景算法为主设计为辅没有一道废题2.1 整卷结构与分值分布当年这份试卷一共安排了四道编程题和两道设计题考试时间120分钟。从结构上看它非常典型地代表了那个年代互联网公司笔试的风格不考选择题、不考概念填空直接上手写代码。四道编程题分数占比约七成设计题占三成考试环境是纯白板或者在线OJ允许使用自己熟悉的语言。这种出题思路背后的逻辑值得玩味。2013年的美团正处于业务快速扩张期技术团队需要的人必须“上手就能干”。算法题考察的是逻辑思维的严谨性设计题考察的是对业务场景的理解力两者结合才能筛选出既能写代码、又懂业务的人。相比今天很多公司动辄四轮面试、每轮都靠背题踩点通过的现状这套试卷反而更加务实。有个细节很有意思这套试卷里没有任何一道题涉及具体的框架或语言特性比如Spring、MyBatis、Java 内存模型等。这说明2013年美团对基础研发岗的定位非常清晰——框架可以进来再学但算法思维和工程直觉必须提前具备。这个理念放到今天依然成立甚至更关键。2.2 每道题背后考察的能力模型我把这套卷子里的经典题目逐一还原并标注了它们对应的能力模型方便你对照自测。下面这个表格是我根据自己的面试和带人经验整理出的核心观察题目类型核心考点考察能力今天的变体二分搜索变体边界条件处理代码严谨性在排序数组中查找元素的第一个和最后一个位置链表反转/排序指针操作熟练度基本功扎实度K个一组翻转链表动态规划状态定义与转移抽象建模能力编辑距离、打家劫舍系列系统设计容量预估与架构取舍工程全局观秒杀系统设计、红包系统设计单看考点本身这些题目并不算难。但笔试的残酷之处在于限时两个小时里要写完四道能运行的代码还要留出时间做设计题对代码速度和思维敏捷度的要求非常高。我当时模拟时给自己掐表发现如果每道题思考超过15分钟后面就会很被动。我建议你也按真实考试环境来模拟不要一道题想半小时实在没思路先跳过做完了再回头补。这种时间管理能力本身就是笔试考察的一部分很多人实力足够但栽在节奏乱了。2.3 对比今天的面试变与不变把2013年的试卷和今天美团、头条、快手的题目放在一起对比你会发现一个明显的“变与不变”。不变的是核心算法考点二分、DP、链表、树依然占据笔试的大半壁江山变的是题目包装更复杂了以前直接让你“实现二分查找”现在会包装成“在魔法森林里寻找灵药”之类的场景题但本质还是二分。还有一个显著变化是并发和分布式的内容加重了。2013年的设计题可能只需要你设计一个订单号生成器今天的系统设计题动辄就是“设计一个支持千万QPS的秒杀系统”。这背后的原因是技术架构的演进2013年是单体应用为主现在是微服务和分布式事务的天下。但我想强调一个观点越是看起来复杂的包装越考验基本功。如果你能把2013年这份试卷里的算法题吃透理解了二分为什么左闭右开、DP为什么这样定义状态那么今天的大多数笔试题对你来说都是“换汤不换药”。根基不牢的人背再多新题也没用。3. 核心算法题精讲题目、解法、变体一次讲透3.1 二分查找的极致变形你真的会写二分吗美团2013年试卷里有这样一道题“给定一个有序数组和一个目标值要求返回目标值在数组中第一次出现的位置如果不存在则返回-1。”这道题乍一看很简单但它在代码实现里的陷阱非常多尤其是边界条件的处理能直接反映一个程序员的代码功底。大多数人的第一版代码会写成这样public int findFirst(int[] nums, int target) { int left 0, right nums.length - 1; while (left right) { int mid (left right) / 2; if (nums[mid] target) { return mid; } else if (nums[mid] target) { left mid 1; } else { right mid - 1; } } return -1; }这段代码能通过普通的查找测试但一旦数组中有重复元素它返回的就不一定是第一次出现的位置。比如{1, 2, 3, 3, 3, 4, 5}目标值是3这段代码返回的可能是中间那个3而不是第一个3。这在一个真实业务场景里会导致严重问题——比如你在订单表中按状态字段查找第一条符合条件的记录结果返回了中间的一条后续逻辑全部错乱。正确的解法是在找到目标值后不立即返回而是继续向左收缩右边界直到左边界越过右边界此时左指针指向的位置就是第一次出现的位置。上面这道题正是后来大厂面试中“在排序数组中查找元素的第一个和最后一个位置”的原型。你如果能理解这个变体的核心思想——为什么找到目标后还要继续收缩——就说明你真的理解了二分而不是单纯背模板。类似的边界陷阱在二分里还有不少比如取中位数时用(left right) / 2可能溢出应该写成left (right - left) / 2循环条件用还是取决于你对区间开闭的定义。这些细节都是笔试中的扣分点也是拉差距的地方。3.2 链表反转的递归与迭代手撕代码的试金石链表相关的题目在2013年试卷中有一道是“反转单链表”。这道题被认为是代码基本功的试金石因为它的迭代解法只有几行但指针指向稍不留神就会写错递归解法更是考验对递归栈的理解。迭代解法的核心是三个指针prev前驱、current当前、nextTemp临时保存后继。每次循环做三件事保存当前节点的后继把当前节点的后继指向前驱然后三个指针整体向后移动。复杂度是O(n)空间是O(1)。public ListNode reverseList(ListNode head) { ListNode prev null; ListNode current head; while (current ! null) { ListNode nextTemp current.next; current.next prev; prev current; current nextTemp; } return prev; }递归解法更简洁但理解门槛稍高它的核心思想是先反转后面的链表再把当前节点接到反转后的链表末尾。需要注意的是递归反转后的链表末尾是head.next需要将head.next.next指向head同时把head.next置空否则会产生环。这道题在今天同样是高频考点而且出现了大量变体K个一组翻转链表、反转链表的一部分区间、链表内每两个节点交换位置等。把基础反转吃透这些变体基本都是在其上做坐标推演。我在指导新人时经常让他们先在白纸上手动模拟一遍迭代过程把每一轮循环后三个指针的指向画出来画完再写代码。这个习惯能帮你把指针操作的逻辑固化在脑子里考试时就不用现场推了。3.3 动态规划数位DP与状态定义的智慧2013年的试卷里有一道DP题原题记不完全了但核心思路和“数字翻译成字符串”很像给定一个数字序列按照某种规则翻译成字符串问有多少种不同的翻译方法。这类题的难点不在于递推公式本身而在于状态定义和边界情况的处理。以 LeetCode 的“把数字翻译成字符串”为例和当年美团题的思路同源状态dp[i]表示前 i 个数字的翻译方法数。转移逻辑是如果第 i 个数字单独翻译那么dp[i] dp[i-1]如果第 i-1 和第 i 个数字可以组合翻译即组合数在10到25之间那么dp[i] dp[i-2]。初始状态dp[0] 1, dp[1] 1。public int translateNum(int num) { String s String.valueOf(num); int n s.length(); int[] dp new int[n 1]; dp[0] 1; dp[1] 1; for (int i 2; i n; i) { String sub s.substring(i - 2, i); dp[i] dp[i - 1]; if (sub.compareTo(10) 0 sub.compareTo(25) 0) { dp[i] dp[i - 2]; } } return dp[n]; }这道题真正想考察的不是你会不会背转移方程而是你能否在“数字0的处理”上踩住边界。比如“01”不能翻译成字符但“10”和“20”可以这个细节决定了代码是否正确。很多人在这个点上想当然导致结果偏差。还有一种更省空间的写法因为dp[i]只依赖前两个状态完全可以用两个变量滚动更新把空间复杂度降到O(1)。这也是面试官喜欢追问的一个优化点你主动优化展示出来的代码审美通常能加分不少。3.4 设计题从订单号生成器看系统设计的基本功设计题部分美团当年的试卷里有一道“设计一个分布式环境下的订单号生成器”。这道题非常经典因为团购业务的订单量天然带有高并发属性数据库自增主键在分布式场景下根本无法满足需求。一个常规方案是“时间戳 机器ID 序列号”。时间戳精确到秒机器ID标识不同服务器序列号是每台机器上自增的计数。这样做的好处是生成的订单号趋势递增、基本趋势有序同时能保证全局唯一。它的核心问题和可能的改进方向正好是今天雪花算法Snowflake设计的雏形。我当时在这道题上拿到不错的分数原因是我不仅给出了方案还分析了这个方案的边界情况比如机器时钟回拨会导致ID冲突服务器时间不一致会导致订单号乱序。这种“主动找茬”的思路恰恰是设计题拉开分数差距的地方。对应到今天的面试这道题的升级版是“设计一个红包系统”或“设计一个秒杀系统”核心考察点依然是三个唯一性如何保证、高性能如何支撑、数据一致性如何取舍。把这套方法论提炼出来你就能应对大部分系统设计题。4. 真题实战复盘我用这份卷子做了一次完整模拟4.1 模拟环境搭建限时、白板、无IDE辅助为了原汁原味还原当年笔试的真实状态我特意给自己搭建了一个模拟环境一台没有安装任何IDE的裸机只有一个纯文本编辑器和命令行编译器总时长设置为120分钟中间不允许查资料、不允许切出屏幕。为什么要这么折腾因为在真实笔试中IDE的自动补全和错误提示都是不允许的你必须依靠自己对语法和API的熟练度。很多人在IDE里写得顺风顺水一到白板就大脑空白这就是因为过度依赖工具的提示能力。提前在无IDE环境中训练能极大降低这种风险。我把模拟考试的时间分配设为编程题每题25分钟设计题每题30分钟最后留10分钟整体检查。实际执行下来编程题的时间相对充裕但设计题比较紧张如果不提前在脑中形成框架很容易写到一半发现逻辑漏洞。4.2 答题过程中的真实卡点与应对我在模拟过程中最卡的一道题是二分查找变体。按理说这种题很基础但一旦要求“返回第一次出现的位置”我第一版代码仍然写成了找到即返回。这时暴露出的问题是很多人的模板是从网上背来的只是机械地记住了while (left right)但没理解这套模板的真正适用场景。我当时的应对策略是停下来在草稿纸上画了一个数组手动走一遍带重复元素的用例观察左右指针的变化过程。画到第三步时我突然意识到只要在nums[mid] target时不返回而是把right mid - 1最后循环结束时的left就是答案。这个顿悟说明了一个真理复杂边界问题画图永远比空想高效。模拟结束后我做了一个复盘表把所有卡壳的点和原因都记录下来。这个过程比做题本身更重要因为它帮你定位了自己思维中真正薄弱的环节——是边界条件容易漏还是对递归过程理解不透还是状态转移方程里的某个分支容易想当然。清楚了这些后续的针对性训练才有方向。4.3 从答卷质量看面试官想看到什么根据我多年参与面试和校招的经验面试官批改笔试答卷时注意力会集中在三个地方第一代码能不能跑通边界用例。大多数人写的代码在常规用例下都能通过但面试官会故意代入空数组、只有一个元素、所有元素相等、目标值不存在等极端情况。能在这些用例下依然正确的代码会被标记为“代码稳健”。第二代码风格是否整洁。变量命名是否语义化、有没有多余的重复代码、临时变量是否用对了地方这些细节直接影响面试官对候选人工程素养的评价。即使是白板代码一个清晰的风格也能留下好印象。第三是否暴露出额外的思考。如果你在代码注释中写出了“这里我用左闭右开区间是为了避免边界溢出”或者在设计题的末尾补充了“该方案在时钟回拨场景下存在问题”面试官会认为你有主动思考的能力这在评分中是一个隐含的加分项。很多人在准备笔试时只专注于“解出题”忽略了这三点。但你要明白笔试的本质是筛选而筛选的标准不仅仅是“对了多少”更是“对一个题时表现出的工程师素质有多高”。5. 常见问题与避坑指南那些年我踩过的笔试的坑5.1 高频错误Top榜边界条件与API误用我统计了自己和身边朋友刷这套题时的常见错误集中在以下几类每一条都是实际踩过的坑希望你能直接避开。第一件事二分查找的停止条件写错。很多人用while (left right)还是while (left right)全凭记忆没有一个统一的区间定义。建议你固定用一种写法并理解它比如我习惯用左闭右闭区间那么条件就是while (left right)更新分别是left mid 1和right mid - 1。只要定义不漂移代码就不会有歧义。第二件事链表反转时忘记处理空指针。当链表为空时current已经是null循环体里的current.next会抛空指针异常。这不是算法能力问题是边界意识问题。每次操作链表前先问自己一句这个节点可能为null吗第三件事动态规划数组越界。比如前面提到的数字翻译题dp[1]依赖于dp[0]如果你初始化时只设了dp[0]访问dp[1]就会越界。这类问题的排查方法是把数组长度加一放一个哨兵位可以极大降低思考成本。5.2 时间分配策略什么题该放弃什么题必须拿分一套试卷做下来时间管理的重要性不亚于技术能力。我见过太多实力过关的人因为在一道题上死磕太久导致后面的题没时间做最终总分很低。根据这套试卷的难度分布我建议你执行以下策略前两分钟快速浏览全部题目标出自己熟悉的题和陌生的题优先做有把握的题哪怕它分值低因为“拿到分”比“挑战难题”更重要一道题如果思考超过十五分钟仍无头绪立刻标记跳过有时间再回来。设计题不要直接写长篇大论先用三分钟列一个提纲框架再逐步填充。有一个技巧特别值得分享在做最后一道题之前给自己留五分钟做全卷检查重点看代码中有没有return缺失、数组越界这类低级错误。这些错误在电脑上运行时会直接报错但在白板笔试中往往不直观容易逃过自查。5.3 笔试通过后如何把这次模拟转化为面试优势很多人把笔试和面试割裂开来这是一个巨大的误区。实际上笔试中的解题思路、你对边界条件的敏感度、设计题的取舍逻辑都会在面试的算法轮中被精准回顾。面试官拿到你的笔试答卷后经常会挑其中的某道题问你“当时为什么这样实现”“还有没有更优的解法”。所以笔试结束后不要急着丢掉题目花三十分钟做一次深度复盘。把每道题的所有解法、复杂度分析、边界条件整理成笔记并尝试用一句话向别人讲清楚你的思路。如果你能教会别人说明你是真的掌握了而不是碰巧AC了。一个有效的加分做法是在面试时主动提起笔试中的某道题并说出你事后发现的更优解法。这会让面试官认为你有成长型思维而不是完成任务就结束。这种印象分往往比多回答对一个面试题更值钱。5.4 试题之外的合规提醒哪些技术方向要格外谨慎在刷题和搜索相关资料的过程中我看到不少与“美团技术”相关的热门搜索词涉及逆向、签名破解、接口模拟等灰色方向。这里我必须明确说一句这些方向不仅违反平台规则也涉嫌违法而且对真正的技术成长没有任何正向价值。作为工程师我们应该把精力投入到算法、架构、工程化等正当领域而不是琢磨怎么绕过别人系统的安全机制。大厂面试中面试官也会考察候选人的职业底线。如果你在技术社区里发布过破解类内容或者GitHub上有相关仓库背调时被发现的概率并不低。这比算法题做不出来要严重得多可能直接进入黑名单。做一个有底线的工程师才能走得更长远。这份2013年的笔试卷之所以值得做正是因为它代表了一种纯粹的技术追求——靠实力说话而不是靠旁门左道。6. 从2013到2025这套笔试卷带给我的三个核心启示拿着这份老笔试卷我最大的感受是技术栈会过时框架会被替代但算法思维、工程素养和学习能力这三种底层能力是超越时间的硬通货。无论你是刚准备校招的应届生还是工作多年想进阶的工程师都可以从这套题里获得实实在在的养分。第一层启示是扎实的基础永远是对抗不确定性的最好武器。2013年的人想不到今天会有ChatGPT和AI辅助编程但他们刷过的二分、DP、链表题在今天依然是面试的必考点。技术进步的速度很快但计算机科学的基础理论演进很慢慢到值得你花三五年去打磨。第二层启示是动手实践比围观和收藏更重要。很多人把笔试题保存到收藏夹里就再也不看了这和买书不看没有区别。真正让你成长的是每个周末抽出两个小时关掉消息通知老老实实地把一套题限时做完再花时间复盘。这种刻意练习不在乎量而在乎每一次都有明确的改进点。第三层启示是保持长期主义的学习心态。我在带团队时发现刚工作两三年的工程师最喜欢锐气什么新框架都要学反而忽视了基本功。而工作五六年之后的工程师开始意识到算法和底层原理的重要性可惜时间已经被业务占满。如果你想避免这种遗憾最好的切入时间就是现在从做一套2013年的笔试卷开始不丢人反而很酷。我在实际准备面试的过程中最受益的习惯是把历年大厂的笔试卷都翻出来做一遍做完不强求满分而是对比自己卡壳的点在哪里。2013年美团这份卷子是我做过的性价比最高的一份老题题量适中、考点经典、设计题有深度强烈推荐你也来一遍。