ARTICLE DETAIL

资讯详情

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

2016美团笔试题复盘:基础为王,经典考点至今仍是面试高频

2016美团笔试题复盘:基础为王,经典考点至今仍是面试高频 1. 回到2016这份卷子考的是“底子”不是“花活”把2016年美团研发工程师笔试题重新翻出来逐题复盘一开始是出于好奇——想看看八年前的大厂校招到底在考什么。做完第一遍之后我反而有点感慨这份卷子没有刻意追求框架、追热点、考“新东西”满眼都是数据结构、操作系统、计算机网络、语言基础这些底层的硬功夫但它的淘汰率一点都不低原因也很简单真正能把基础题做到滴水不漏的人从来都不多。当时美团正处于“千团大战”结束、和大众点评合并后的整合期外卖、到店、酒旅等业务都在快速扩张服务端面临的是高并发、海量订单、实时调度这类非常接地气的技术挑战。那一年校招研发岗位的笔试核心目的就一句话筛出基本功扎实、遇到问题能拆解、写代码有章法的人。注意它并不是要筛出“背过最多题解”的人。所以这份卷子的整体气质是覆盖面广、单题难度适中、但处处埋着“懂没懂”的探针。比如一道选择题表面问的是“进程和线程的区别”实际追问的可能是“同一个进程里两个线程的栈是共享还是独立”不少人在第一步答对在第二步暴露理解不深。笔试后进入面试环节很多问题就是从卷面答案开始切入的考官会顺着你的答案一直往下追问答得越脆印象分越高。这篇文章我想做一次相对完整的复盘先还原试卷结构和考点分布再把高频考点按模块拆开挑几类典型的题目讲透最后聊一聊这套题对今天准备研发岗位的人还有没有参考价值。如果你正准备校招或者工作三五年后想回头补一补基础这份复盘应该能给你一些不一样的视角。2. 试卷整体复盘模块、题型与分值结构2.1 各模块考点分布把这份试卷的题目按领域归类会发现分布非常集中几乎没有偏题怪题。大体可以分为五块模块典型考点大致题目占比数据结构与算法二叉树遍历、链表、堆、动态规划、字符串处理30% - 35%操作系统进程与线程、死锁、内存管理、文件系统15% - 20%计算机网络TCP/UDP、HTTP、DNS、滑动窗口10% - 15%编程语言基础C 内存模型、Java 集合与并发、常见关键字15% - 20%逻辑与系统设计容量估算、短链设计、场景方案题10% - 15%从比例上看算法和数据结构绝对是大头这和几乎所有互联网公司的校招笔试逻辑一致算法是快速过滤候选人的最优工具因为它测试的是长期积累的解决问题的能力而不是短期背诵的工程框架知识。不过要注意2016年的算法题跟现在很多平台上的“困难偏题”风格不一样它更偏重经典模型比如TopK、树的公共祖先、链表的环、最长子序列这类题目本身不会绕很多弯但要求你在限时内写出的代码能跑通、边界条件合理、复杂度说得清。2.2 选择题与编程题的侧重点差异这份试卷的选择题数量不少覆盖了很多“默会知识”。现在回头看这些选择题其实比编程题更能暴露一个人的知识体系是否完整。举个例子操作系统模块中考察了“死锁的必要条件”和“页面置换算法”网络模块中考察了“TCP四次挥手过程中TIME_WAIT出现的时机”这类问题如果只是考前突击背答案很容易在换一种问法时翻车。编程题则完全是另一条考察线不考记忆只考动手。题目量大概在三到四道左右要求手写完整代码并且对输入输出格式有一定要求。这背后传递了一个信号美团当时需要的是来了就能干活的人哪怕只是实习生也希望你具备“把思路翻译成可运行代码”的基本能力。我在复盘时把每道错题都重新写了一遍发现最遗憾的不是不会做的题而是那些“我会但时间不够”的题。这说明做题速度本身也是一种能力尤其是手写代码时的代码组织效率。后面我会专门聊备考节奏这里先提一句如果现在让你限时二十分钟手写一个二叉树的层序遍历你可能也要想一会儿。3. 高频考点题解从一道题看一类题3.1 算法题TopK问题的三种解法与复杂度博弈TopK在2016年的笔试中几乎是“标配题”放到现在也依旧是面试高频。常见的问法有两种海量数据中找最大的100个数、或者从一个数组中找出第K大的数。别看题目简单背后有非常清晰的技术选型逻辑。第一种思路是“全排序后取前K个”。直接把所有数据排序然后取索引为0到K-1的元素。时间复杂度是O(n log n)空间复杂度O(n)。这个方案只在数据量可控时成立一旦数据规模到了“单机内存放不下”的程度排序方案就彻底失效了因为你要么必须借助外部排序要么根本没有足够的空间存中间结果。第二种思路是“维护一个大小为K的堆”。找最大的K个元素就维护一个小顶堆找最小的K个元素就维护一个大顶堆。为什么方向是反的因为堆顶必须是“最容易被淘汰的那个”这样每来一个新元素只要和堆顶比较如果新元素更大就替换堆顶然后重新调整堆。整个过程的复杂度是O(n log K)空间复杂度只有O(K)在海量数据场景下非常友好。即便数据分布在多台机器上也可以先对每台机器取局部TopK再把各机器的TopK合并做一次整体TopK这个思路在美团的实际业务中很常见比如从多个订单分库中统计价格最高的订单。第三种思路是“基于快速排序的partition”。快排的partition操作能把数组分成“小于基准值”和“大于等于基准值”两部分利用这一点每次只需要递归处理包含目标K的那一侧平均时间复杂度降到O(n)是最优的理论值。但要注意快速选择的最坏时间复杂度是O(n²)当数据分布很差时可能退化而BFPRT中位数的中位数算法可以保证线性时间但常数较大笔试面试中一般不需要写BFPRT能讲清楚思路就足够了。解法时间复杂度空间复杂度适用场景全排序O(n log n)O(n)数据量小代码简单堆解法O(n log K)O(K)海量数据流内存受限快速选择平均O(n)最坏O(n²)O(1)数据量适中追求速度我复盘这道题时最大的感受是不要只会写解法要能说清楚每种解法的适用边界。如果面试官追问“数据量大到分布在不同机器上你怎么处理”“数据是流式到达的你还能用排序吗”只会写一种解法的人就很容易被问倒。3.2 算法题最近公共祖先LCA的递归与优化LCA是二叉树问题里的经典题也是2016年笔试中很有代表性的一道。题目形式很朴素给一棵二叉树和两个节点找到它们的最近公共祖先。最直接的解法是递归。思路是从根节点出发做后序遍历如果当前节点是空返回空如果当前节点等于p或q直接返回当前节点然后分别在左子树和右子树中查找。查找结果分成三种情况左右都非空说明p和q分别落在左右子树当前节点就是LCA只有一侧非空说明两个节点都在那一侧返回这一侧的结果两侧都为空返回空。TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if (!root || root p || root q) return root; TreeNode* left lowestCommonAncestor(root-left, p, q); TreeNode* right lowestCommonAncestor(root-right, p, q); if (left right) return root; return left ? left : right; }这十几行代码写起来不难但内含两个关键点。第一递归返回值的设计很巧妙它同时承担了“是否找到了目标节点”和“已经找到的LCA”两个语义。第二时间复杂度是O(n)空间复杂度在最坏情况下是树高O(n)对一棵链状二叉树来说递归深度会很大这是手写代码时容易忽略的边界条件。如果进一步追问“多组查询怎么做”答案就要升级了。可以对树做预处理用倍增法把单次查询降到O(log n)空间复杂度O(n log n)也可以用Tarjan离线算法在O(n q)内处理完所有查询。这类扩展思路在笔试里不一定要求写代码但能说出来会显著加分。我在复盘时觉得这道题最大的价值是训练“递归思维”。很多人看到二叉树问题就想着怎么迭代遍历其实递归才是解决树问题最自然的方式。先把递归版本写到闭眼能默写的程度再去追求迭代、优化和边界控制顺序不能反。3.3 操作系统题死锁判定与银行家算法操作系统模块里死锁几乎是每次笔试都绕不开的点。2016年的题目侧重于基础概念但真正答好的人不多。死锁的四个必要条件是互斥、持有并等待、不可剥夺、循环等待。卷面上经常给一张资源分配表问你“当前系统是否处于死锁状态”。这时候第一步不是背条件而是先把每类资源的已分配量Allocation、还需量Need、可用量Available整理成表格然后判断是否存在一个“安全序列”——即某进程的全部需求可以被当前可用资源满足满足后运行完毕并释放资源再去满足下一个进程。以三个进程P1、P2、P3两类资源A和B为例。假设系统资源总量为(5, 4)当前AllocationP1(1, 1)、P2(2, 0)、P3(1, 1)那么Total减去所有Allocation就是Available得到(1, 2)。再假设NeedP1(2, 1)、P2(1, 1)、P3(1, 2)。逐个检查发现P2的Need(1, 1)小于等于Available(1, 2)先执行P2释放后Available变成(3, 2)然后P3可以执行最后P1执行。所以系统处于安全状态未死锁。这类题的关键在于别把Allocation和Need搞混更不要跳过安全序列的验证直接下结论。我在做这类题时养成了一个习惯无论题目给出的状态看起来多安全都老老实实把执行序列写出来因为手一滑漏算一个进程结果就完全错了。3.4 计算机网络题TIME_WAIT为什么非要等2MSL网络模块里TCP连接管理几乎是必考项。2016年那套卷子对TCP的考察并不局限在“记住三次握手是SYN、SYN-ACK、ACK”这个层面而是更倾向考察连接状态迁移和协议设计动机。TIME_WAIT是最容易暴露理解深度的一道题。当TCP连接主动关闭方发出最后一个ACK就会进入TIME_WAIT状态等待2MSL最大报文段生存时间后才真正关闭。这个等待有两个作用第一保证最后一次ACK能够到达对方。如果这个ACK在网络中丢失被动关闭方会超时重发FIN主动关闭方此时仍处于TIME_WAIT可以再次回应ACK如果没有TIME_WAIT主动关闭方直接进入CLOSED对方重发的FIN就无人应答连接状态会错乱。第二让属于旧连接的所有报文在网络中自然消亡。假设一个TCP连接的报文因为网络延迟滞留在链路上新的连接恰好使用了相同的四元组旧报文就可能被当成新连接的数据包处理造成数据错乱。等待2MSL之后旧报文在网络中已经被丢弃新连接才足够安全。理解了这个机制再看“服务端出现大量TIME_WAIT怎么办”这类实际运维问题思路就会清晰很多。TIME_WAIT只出现在主动关闭方所以高并发服务中如果服务端主动断开连接就会出现大量TIME_WAIT占据连接资源。常见的优化手段有开启TCP_TIMEWAIT_REUSE需要配合时间戳选项仅对出站连接有效、避免服务端主动关闭连接、调整内核参数。但请注意tcp_tw_reuse并不能减少服务端TIME_WAIT的数量它只允许客户端在发起新连接时复用处于TIME_WAIT的端口很多人在这里理解错位。3.5 语言基础题C内存管理对比Java垃圾回收2016年美团笔试的语言基础部分很有时代特色既考C又考Java这跟当时美团和点评两个技术体系合并的背景有关。用今天的视角看这部分题目反而是最“复古”的但它的核心考点至今仍然有效。C方向的经典问题是“malloc/free和new/delete的区别”。malloc是库函数只分配指定字节数的内存返回void指针new是运算符在分配内存后还会调用构造函数完成对象初始化。对应的delete会先调用析构函数再释放内存而free只是释放内存。这个区别背后是C的RAII思想资源获取即初始化资源释放即析构。如果你在笔试里只回答“new比malloc多调用构造函数”其实是没答到点子上面试官想听的是“构造函数和析构函数的存在让对象的生命周期管理变得可控”。Java方向的经典问题是“HashMap的底层实现”和“JVM内存区域”。HashMap在Java 8之后由数组加链表改为数组加链表加红黑树链表长度超过阈值默认8且数组容量不小于64时转为红黑树目的是减少极端哈希冲突下的查询时间。这个考点在2016年已经出现到现在仍然是高频题只是追问会更深入比如“为什么阈值是8”“什么时候从红黑树退化为链表”。对比C手动内存管理和Java自动垃圾回收我建议备考时不要单纯背“手动快、自动省心”这类结论。更好的切入角度是理解两种方案的取舍手动管理让程序员完全掌控对象的生命周期但同时也把犯错的机会交给程序员GC让开发者免于频繁处理内存释放但引入了STW停顿和不可预测性。美团这类重服务端业务的团队内部对GC调优、内存分析的需求非常普遍所以笔试考语言细节本质上是在考察“你能不能看懂线上问题”。3.6 系统设计题短URL服务的容量估算与存储选型如果在复习时只看算法题很容易忽略这套卷子里还有一类偏设计的题目。虽然2016年这类题目占比不高但它们代表了大厂笔试的一个趋势从单纯考代码能力转向考“产品技术方案”的综合能力。比较有代表性的设计题是“如何设计一个短URL服务”。这类题看似开放但回答时要有一条清晰的逻辑链。首先是需求分析短URL服务要解决长链接在传播时过长、容易被截断的问题核心操作是长链接转短链接、短链接访问时重定向到原长链接。其次是容量估算这是设计题中最能体现“工程师素养”的部分。假设每天新增500万条短链接每条长链接平均200字节短链接存储需要几十字节一年的纯数据量大概是500万 × 365 × 300字节约54.75GB加上索引和冗余一年后总存储大约是100GB级别。能算出这个量级后续选型才有依据。然后是方案设计。短链接生成算法有两种主流路线一种是哈希截断对长链接取MD5或SHA1再截取前6到8位作为短码但存在碰撞风险需要加盐或查重另一种是发号器用一个全局自增ID搭配62进制转换生成一个只增不减的短码。后者更可控也更容易支持分布式扩展多台发号器可以分别负责不同号段比如A机器生成1到1亿B机器生成1亿到2亿避免单点瓶颈。存储层通常用Redis做短码到长链接的高性能缓存用MySQL或NoSQL做持久化存储访问跳转时先查缓存缓存未命中再查数据库并回填缓存。重定向状态码这里也有一个细节301是永久重定向可以缓存但不利于统计短链的点击来源302是临时重定向每一次点击都会经过服务端便于记录访问日志。多数短链服务会选择302。这类设计题在今天的面试中已经是常规题型了而且难度只会更高。但核心的分析框架没有变先想清楚场景再做容量估算然后设计核心流程最后考虑扩展和容错。2016年的笔试虽然只给出了一道类似题目但它验证了“笔试不只看你会不会写代码”这个信号。4. 试卷之外的信号美团当时想要什么样的工程师4.1 从笔试题看团队的技术栈选择一份笔试考卷往往是团队技术栈的缩影。2016年美团的笔试题既出现C的虚函数和内存管理也出现Java的JVM和集合类这背后是技术团队的真实构成既有以C为核心的底层基础设施和部分业务后端也有以Java为主的大规模分布式业务系统。笔试同时考两种语言不要求你每一种都精通但要求你至少对其中一种有足够深入的理解同时具备阅读另一种语言代码的基本能力。这里有一个容易被忽视的细节语言基础题里反复出现的“内存”“并发”“引用与指针”等概念其实都在为后续的业务场景做铺垫。美团的核心业务是本地生活服务订单、支付、营销这类系统对并发控制和数据一致性要求极高语言底子不牢的人写出来的代码在极端流量下很容易出问题。所以笔试选择用语言题来筛选是一种成本很低但效率很高的手段。4.2 O2O业务场景在笔试题中的渗透2016年这套笔试题里有一部分题目看起来是纯粹的算法题实际背景却隐含了O2O业务场景。比如配送路径规划问题可以抽象成图论的最短路径或最小生成树商家列表的排序问题可以抽象成多维度的TopK订单超卖问题可以抽象成并发控制与库存扣减。笔试命题人把这些业务场景“脱敏”成经典题目考察的还是基础能力但如果你能在答题时点出题目背后的业务背景往往会加深面试官对你的好感。这种考法的核心逻辑是工程师的价值不在于记住某个框架API而在于能把复杂业务问题抽象成可计算、可验证的技术问题。所以做这套题时我建议不要只刷题还要刻意训练“看穿题目本质”的能力——比如看到“从海量日志中找出访问量最高的10个IP”立刻想到这是TopK看到“多个服务实例抢购同一件商品”立刻想到分布式锁与库存预扣。这种抽象能力不是一两天能练出来的但可以从笔试真题开始培养。4.3 笔试中最容易失分的“非技术因素”技术能力强不代表笔试分数高。我复盘了很多人的答题情况发现失分点往往不在难题上而在一些看似无关紧要的地方。一是输入输出处理习惯不好。有些考生在本地IDE用真实项目里的框架代码写习惯了笔试时连最简单的标准输入读取都写不利索导致代码逻辑对了但编译不通过。二是边界条件考虑不周数组越界、整数溢出、空指针、空集合这些问题在单元测试时很容易暴露但在手写代码环节稍不注意就会漏掉。三是代码风格混乱变量名用a、b、c逻辑不分段面试官看到这样的代码即使功能正确也可能给出较低评价因为他无法判断你是真的会写还是在碰运气。我个人的体感是笔试虽然以“题目答案”为最终交付物但它本质上是一次“代码可读性和工程习惯”的预览。把输入输出、边界条件、命名规范这些基本功练好比多刷十道难题更管用。5. 现在再做这套题怎么用才不浪费5.1 哪些考点至今仍是面试高频很多人看到“2016年”这个年份第一反应是“这题太老了没参考价值”。我的看法刚好相反这套题里至少六成考点到今天依然是面试中的高频问题。二叉树遍历、TopK、死锁、TCP状态迁移、HashMap、JVM内存区域、C智能指针这些问题我在最近几年的校招面试中依然能听到。技术框架会变但计算机基础知识的“半衰期”很长尤其是算法和网络部分几乎不会过时。真正过时的是那些纯语法概念题比如某个C关键字在特定编译器下的行为、某个Java版本的废弃API。但这类题目在当年也未必是决定因素它们更多是起点用来引出更深层次的考察。所以备考时不要因为“题目老”就轻慢反而要把它们当成“基础是否牢固”的试金石。5.2 刷题与复盘的正确节奏如果想把一份笔试真题的价值榨干我建议分三步走。第一步是限时模拟。给自己定一个跟正式考试相同的时间限制比如90分钟全程按笔试的紧张状态来做。不要边做边查资料也不要一道题卡住了就停下来思考半小时这样模拟出来的结果不具备参考性。做完以后先不对答案把每道题的知识点写在旁边。第二步是逐题深挖。每题无论做对做错都问自己三个问题它考的是哪个知识点我为什么用这种方法解有没有更优的解法尤其是做错的题要找出错误原因是“知识盲区”还是“粗心大意”这决定了后续复习的优先级。知识盲区需要系统补课粗心大意则需要通过多轮模拟来训练专注度。第三步是建立错题矩阵。把做过的题按“知识点”和“错误类型”两个维度整理成一张表格比如“二叉树—边界条件漏判”“TCP—状态迁移记忆混乱”。这张表能直观告诉你薄弱的到底在哪一块比漫无目的地重新刷题高效得多。我当年备考时就是用这个办法把错题从最多的“动态规划”逐渐压到接近零。5.3 从笔试到面试把答案讲成“自己的理解”最后想分享一个容易被忽略的认知笔试只是起点面试官更关注你答题背后的思维方式。美团2016年的校招流程中笔试通过后进入面试面试官手里是能看到你卷面作答情况的很多面试问题就会直接从你的“错误答案”或“模糊表述”切入。所以备考时不要满足于“看到正确答案”而要把每道题都练到能口头讲出来的程度。比如一道“为什么TCP需要三次握手”的选择题你不仅要选对还要能解释“如果是两次握手会出现什么情况”“历史上为什么不用四次”。这种表达能力不是临时抱佛脚能练出来的需要平时就刻意训练。我自己的经验是每做完一套题挑三道最典型的题目用五分钟时间假装给一个新人讲一遍讲不清楚的地方就是还没真正理解的地方。这套2016年的卷子我后来又翻过两遍每次都有新发现。第一遍看的是答案第二遍看的是出题逻辑第三遍看的是自己的知识盲区。有一点体会特别深笔试不是让你展示“知道多少名词”而是让面试官在有限的题目里看到你面对一个不确定的问题时能不能快速定位考点、选择合适的解法、并把解法转化成可运行的代码。能做好这一点比记住多少题都重要。
返回列表