ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

美团研发笔试复盘:从算法基础到工程思维的考察逻辑

美团研发笔试复盘:从算法基础到工程思维的考察逻辑 很多从 2016 年走过来的研发同学对美团这套笔试题应该都有印象。那会儿移动互联网正值扩张期美团的笔试向来以“基础扎实、边界抠得细、题量看着不大但坑不少”著称。我是那一年参加的笔试“二卷”印象尤其深——不是因为它题目多难而是它每一道题都在逼你回答同一个问题你是真的理解还是只是背过答案。这篇文章不是去复现原题毕竟题目本身也不允许违规传播而是结合我对那套题的复盘、后来参与校招命题的经验把这类试卷背后的考察逻辑、做题思路、易错点拆开来讲。适合正在准备大厂研发岗笔试的同学、想系统补算法基础的非科班朋友以及那些“刷了很多题但一上考场还是慌”的人。1. 2016年美团研发笔试考什么能力模型与出题逻辑1.1 考卷的整体框架与考察层次美团 2016 研发笔试题二这份卷子整体结构基本是“选择题 编程题 少量问答题”的组合覆盖了四个层次计算机基础知识数据结构、操作系统、网络协议、数据库索引等主要集中在选择题里。逻辑与数学思维考概率、排列组合、递推关系这层和算法题直接挂钩。代码实现能力编码题考察对常见算法和数据结构的落地能力不是背模板就行。工程思维通过隐藏条件、边界条件、复杂度限制来筛选这个比前面几项更能拉开差距。那会儿互联网笔试还不流行“系统设计题”铺满全场但美团已经会在编码题里埋一些工程化的坑比如输入输出规模、内存限制、会不会超时。你光会写一个能跑通的解法不够得学会判断什么场景下用什么方案。1.2 出题人最想看到的三种信号我在后来的面试官经历里越来越理解出题人的心态。笔试不是要筛出“算法竞赛选手”而是要看候选人身上有没有这三类信号拆解问题的能力。拿到一个陌生题目第一步不是写代码而是把题目翻译成数据结构问题、边界条件、时间空间约束。这个能力在三到五分钟内就能看出来。代码的“防御性”。数组越界、空指针、极端输入这些不是考察记忆力而是考察你有没有形成肌肉记忆。美团那套题里尤其爱在边界条件上做文章。复杂度直觉。不会要求你证明一个复杂算法的每一步但你必须知道 n10^5 时 O(n²) 大概率过不了必须知道排序为什么是 O(n log n) 而不是 O(n)。这三条放在今天依然是研发岗笔试的核心逻辑2016 年的美团卷只是比较早地把这套标准落到了试卷上。2. 一道编码题背后的“压榨式”考察逻辑2.1 从题面到约束条件的三步拆解我还记得二卷里有一道排序和查找结合的题题面看似简单给一个无序数组求第 K 大的数。大约一半人第一反应是“排序然后按下标取”这就是典型的没有拆解约束条件。真正的做题流程应该是这样三步第一步确认数据规模。如果 n 很小小于 1000排序取下标完全没问题。但如果 n 是 10^6你就要意识到排序是 O(n log n)虽然可能过但面试官更希望看到快速选择quick select或者堆的解法。第二步确认 K 是相对位置还是绝对位置。第 K 大 和第 K 小、从 1 开始还是从 0 开始、有没有重复元素这些都会导致答案完全不同。把题面翻译成数学符号是做题前最重要的几分钟。第三步确认内存限制。如果内存非常紧张快速选择的空间复杂度是 O(1)堆是 O(K)而排序需要 O(n) 的额外空间因为语言自带的排序不保证原地这个细节也会影响选型。我当时在试卷上先用 5 分钟写了暴力解法的思路再用快速选择实现最后补了一段边界处理说明。这么安排不是因为暴力解值得写而是阅卷人会看到你在面对问题时有一个“由易到难、逐步逼近”的过程这个印象分非常重要。2.2 为什么“先写暴力解”反而是加分项很多同学怕写暴力解被扣分但我见过大量实际阅卷案例完全空白的代码最致命其次是直接写一个非常“炫技”但边界全是洞的解。先写暴力解至少传达三个信息你看懂了题目能正确模拟这个过程。你具备“优化”的基础因为暴力解暴露了性能瓶颈后续所有优化都有据可依。遇到想不出最优解的情况暴力解能保证你拿到部分分数——笔试是踩点得分不是满分或零分。我记得二卷有一道链表相关的题最简单的做法就是遍历两次第一次算长度第二次找位置。能做 O(n) 两次遍历已经击败很多人了因为不少人在“快慢指针”上纠结太久最后没写出来。笔试考场上稳定输出比惊艳但没写完整更重要。3. 高频考点拆解三类让我印象深刻的题目3.1 排序与二分边界条件是核心得分点美团那套题里的排序和二分从来不直接问你“快排怎么写”而是把二分藏在“寻找某个条件满足的最小/最大位置”这种场景里。我记得有一题是找一个递增序列中第一个大于等于目标值的位置。很多人会写成这样int lower_bound(vectorint nums, int target) { int l 0, r nums.size() - 1; while (l r) { int mid (l r) / 2; if (nums[mid] target) l mid 1; else r mid; } return l; }这段代码能过大部分用例但有两个坑值得注意如果整个数组都小于 target最后返回的 l 会等于 nums.size()调用方必须判断越界。如果数组为空nums.size()-1 在无符号类型下会变成超大值直接进入死循环。这两个坑在那年卷子的选择题里也出现过——它不直接考二分而是给你一段代码让你选“当输入为空时会发生什么”。你要是不习惯先检查输入很容易踩进去。后来我在实际开发里写二分查找都会强制自己先处理空数组和首尾边界这已经成了写类似逻辑的习惯。3.2 链表操作的指针本质不是背题是理解对象引用链表题是笔试常客美团 2016 二卷里那道反转链表的题我印象很深刻。迭代版本的核心代码其实很短ListNode* reverseList(ListNode* head) { ListNode* prev nullptr; ListNode* cur head; while (cur) { ListNode* next cur-next; cur-next prev; prev cur; cur next; } return prev; }但很多人会在cur-next prev之前就丢了next节点导致链表断裂。这就是对“节点引用”理解不透彻——你修改一个节点的next指针时原先通过它才能访问到的那一部分如果没有提前保存就再也找不回来了。这个道理放在日常开发里就是“在修改一个对象前先搞清楚谁还在引用它”。我在做数据迁移、重构老代码的时候经常遇到类似问题你以为改了某个字段没关系结果另一个模块还在用它做链路跳转。链表题练的不是指针语法而是“在你动结构之前先想清楚依赖关系”的思维方式。3.3 动态规划的“状态定义”从状态转移中找回丢失的思考二卷里有一道最大连续子数组和经典的 Kadane 算法考察点不是你会不会背公式而是你能否自己定义出“以 i 结尾的最大子数组和”这个状态。def max_subarray_sum(nums): if not nums: return 0 cur nums[0] best nums[0] for x in nums[1:]: cur max(x, cur x) best max(best, cur) return best这道题的价值在于cur max(x, cur x)这句代码背后是一个“要么从这个位置重新开始要么接着前面的累加”的决策。如果你只是背下来这句遇到变体题二维矩阵最大子矩阵、环形数组最大子数组和就会完全懵掉。理解状态定义之后你就能自己推导出环形数组的解法把问题拆成“不跨越边界”和“跨越边界”两种情况后者等价于总数组和减去最小子数组和。这种扩展能力不是靠刷题量堆出来的而是靠每道题都要想清楚“状态为什么这么定义”。4. 时间分配与做题顺序先拿分后打磨4.1 我的答题顺序策略先答有把握的分数2016 年美团二卷的题量看起来不大但每道题都有足够的“深度陷阱”所以时间分配特别重要。我当时给自己定了一个顺序先扫一遍所有题目标记出“一眼就会”“需要想想”“完全没思路”三类。先做“一眼就会”的题快速把基础分拿到手。不要觉得这些题简单就不值得做选择题里可能藏着多个边界条件。再做“需要想想”的题每道题最多给自己 15 分钟15 分钟没有思路就切换到下一道留出最后 10 分钟回来看。“完全没思路”的题放到最后用暴力解或者写清思路的方式混过程序题的部分分数。这个策略最核心的一点一定不要在一道题上死磕超过 20 分钟。笔试是控制时间下的策略游戏不是科研攻关。死磕一题导致后面 20 分的大题没时间写这种事在考场上太常见了。4.2 时间分配的具体参考表题型建议用时策略选择题基础 边界每题 2-3 分钟没把握的标记跳过最后统一回看编程题熟悉题型每题 20-25 分钟先写暴力解再优化再补边界编程题陌生题型每题最多 15 分钟写出核心思路即可不要恋战问答题/设计题10-15 分钟画结构图/写步骤比纯文字得分更稳这个表不一定适用于所有人但“先拿分后打磨”的底层逻辑是通用的。你的目标是在两个小时里让总分最大化而不是答完每一道题。4.3 我踩过的坑选择题上犹豫太久经验往往是踩坑换来的。我考前参加了另一家公司的模拟笔试在一道关于哈希表冲突处理的选择题上犹豫了整整 10 分钟纠结链地址法和开放定址法的细节最后编程题没写完整。那次教训让我意识到选择题的分数权重通常低于编程题但时间投入却更容易失控。从那次以后我一看到选择题超过三分钟没思路就会先在草稿纸上写下几个关键词然后跳到下一题。等整卷答完再回头细想。千万别小看这个微小的改变它可能帮你省下 15 分钟给最值钱的编码题。5. 阅卷人视角边界条件、复杂度与代码规范5.1 边界条件里的经典陷阱作为后来的面试官我看代码时会先看边界处理因为那是最容易暴露“只会写核心逻辑”的地方。2016 年美团二卷整体上非常喜欢在边界上做文章常见的陷阱大概有五类空输入空数组、空字符串、空指针不处理基本直接崩。单元素输入很多解法在 n1 时进入死循环或越界。正负边界整型溢出、负数取模、绝对值最大值。重复元素排序和二分里重复元素会导致边界条件不唯一。无穷大/极小值用INT_MAX表示初始最大值时如果输入本身就包含这个值比较逻辑会出错。我见过不少同学把INT_MIN当成“负无穷”用结果输入里恰好有一个非常小的数答案就错了。更好的做法是用一个布尔变量标记“是否已经初始化”而不是依赖一个绝对极值。5.2 复杂度估算不会精确分析也要会量级估算2016 年那套笔试的编程题不会要求你写出严格的复杂度证明但你必须在心里知道什么量级能过、什么量级会超时。一个简单经验如果 n 是 10^5 级别O(n²) 基本超时O(n log n) 一般能过O(n) 最稳如果 n 是 10^8 级别O(n) 也要谨慎可能需要数学优化。实际笔试里“排序 二分”的组合通常能解决一大半问题。比如你要统计数组中比某个值小的元素个数先排序再二分复杂度是 O(n log n m log n)这在绝大多数场景下都够用。不要一上来就想线段树、平衡树这些高级数据结构——在笔试中简单可靠比高级但容易写错更占优势。5.3 代码规范阅卷人从三行代码里读出你的工程习惯代码规范不是形式主义它直接反映你的工程习惯。我阅卷时看三个点变量命名是否自解释。int k可以但int kthIndex更好。笔试不需要写论文级注释但变量名自带语义能减少沟通成本。循环边界是否清晰。for (int i 0; i n; i)比for (int i 0; i n - 1; i)更不容易出错后者在 n0 时会因为无符号类型出问题。是否处理空输入。能在函数开头写if (nums.empty()) return 0;的人通常也会在线上问题排查时先做防御性判断。这些看似细枝末节的东西在阅卷时会被放大。那一年跟我一起阅卷的同事有一句话“代码写得好不好不是看算法多高级而是看我会不会愿意跟他一起做 code review。”这句话放到现在也不过时。6. 从笔试到研发岗这套题真正想筛出的能力6.1 笔试题就是现实工作的预演后来我开始带新人、做系统设计、排查线上问题才发现当年美团那套卷子里的题目不是孤立的算法题而是研发日常的缩影。二分查找的边界问题对应的是你在处理时间区间查询、分页拉取、日志扫描时到底该用左闭右开还是左闭右闭。链表反转对应的是你在修改一个复杂对象图时如何保证不破坏原有引用链路。最大连续子数组和对应的是你在分析监控指标时怎么快速找出一段时间内的异常峰值区间。与其说笔试在考算法不如说它在预演“你在真实工作中遇到一个未知问题时有没有一套可复用的思考框架”。6.2 我后来面试别人时的出题思路作为面试官以后我也开始出笔试题。我发现我不是在考“你刷过多少题”而是在考“你拿到问题以后会不会慌”。所以我的题目里故意留下模糊条件比如不说明输入是否有重复元素也不说明时间限制然后看候选人会不会主动问清楚。美团 2016 年那套二卷很多题目也有这个特点——题干看起来短但隐藏条件不少。你能不能发现这些隐藏条件决定了你能拿多少分。这是我后来特别想分享给读者的一个建议刷题的时候不要只看题解而是要练“审题—追问—假设—验证”这个闭环。如果能在草稿纸上写出“输入是什么类型、大小范围、有没有重复、内存限制如何”这几个问题你已经在思维上碾压了大多数直接写代码的人。6.3 最后一次经验之谈再说一个实际的小技巧笔试前一周不要大量刷新题而是把以前做错的题分类整理尤其是边界条件出错的题。我当时准备美团笔试时把自己所有的while循环边界、数组是否为空、指针是否为空这三类错误列在一张 A4 纸上考前过一遍考场上的防御性意识会强很多。这套方法放到今天依然有效。毕竟笔试考察的从来不是“你记住了多少”而是“你在有限时间内能稳定输出多少”。这种稳定输出靠的是平时对边界、复杂度、代码规范的刻意训练而不是考前的运气。
返回列表