
蘑菇街2019届校招后端岗位的笔试题我到现在还留着印象。不是因为题目特别难而是它很典型覆盖面广、基础题多、场景题实在基本代表了电商类互联网公司校招后端笔试的主流风格。如果你正在准备校招或者刚转后端没几年想看看大厂笔试题长什么样这篇文章值得花十分钟看完。我当时投的是Java后端方向所以下面这套拆解会以Java技术栈为主线但很多知识点是跨语言的比如网络、操作系统、数据库、算法这些不管用什么语言写后端都躲不掉。我会把题目背后真正想考察的东西讲清楚再给出完整的解题思路和刷题方向顺便把当年踩过的坑也一并交代了。1. 先弄懂蘑菇街这类电商后端岗在校招笔试里到底想考什么1.1 笔试定位不是筛算法竞赛选手而是筛基础扎实的工程苗子很多同学在校招季容易陷入一个误区以为后端笔试就是刷LeetCode把hard题刷明白就能过。但蘑菇街这份笔试题给我的感觉完全不是这样。它更像是一张“基础能力体检表”算法题只占一部分更多篇幅给了计算机网络、操作系统、数据库和Java基础甚至还有场景设计题。为什么这样设计因为电商公司的后端系统本质上是一个高并发、高可用的数据流转系统。蘑菇街作为导购电商平台核心业务包括商品、订单、支付、营销、搜索推荐等模块后端工程师日常处理的就是大流量下的接口性能、数据一致性、缓存策略、消息队列这些问题。笔试阶段不需要你真有生产经验但必须证明你掌握了计算机通用的底层知识并且能把知识往工程场景上迁移。换句话说笔试筛的不是“谁刷题多”而是“谁的基础底盘厚、谁的工程思维在线”。明白这一点复习重心就不会跑偏。1.2 考察范围与能力模型的对应关系我把这套笔试题涉及的模块和它们对应的能力要求整理了一下。考察模块典型题型实际考察的能力数据结构与算法链表、二叉树、动态规划、字符串处理编码基本功、边界处理、复杂度分析Java基础集合源码、并发编程、JVM内存语言深度、多线程意识、调优基础计算机网络TCP握手、HTTP协议、DNS解析网络通信原理、排查问题的基础操作系统进程线程、死锁、内存管理对系统资源的理解、并发底层逻辑数据库索引结构、事务隔离、SQL优化数据建模能力、存储引擎理解分布式/中间件Redis、消息队列、分布式锁架构视野、生产场景的常识储备场景设计秒杀、购物车、优惠券结算综合设计能力、业务抽象能力这个表格基本就是一份“校招后端能力雷达图”。你会发现没有任何一项是脱离实际生产场景的哪怕是Java集合源码的考察也是因为后端代码里天天在用HashMap、ArrayList你要是连扩容机制都说不清面试官很难相信你能写出高性能代码。2. 知识点硬核拆解高频考点背后的原理与答案2.1 数据结构和算法笔试试卷的“题量担当”蘑菇街这轮笔试的数据结构与算法部分难度大约在LeetCode中等偏下但题量不小需要在有限时间内快速准确完成。我印象比较深的几类题是链表操作、二叉树遍历、经典动态规划和字符串处理。链表类题目几乎每年必考。原因很简单链表能同时考察指针操作、边界处理和递归/迭代思维而且写起来容易出错区分度高。比如反转单链表迭代法就是三指针轮流走pre、cur、next三个引用不停地改指向还要特别注意空链表和单节点链表这两个边界。递归法虽然代码短但很多人第一次写会绕晕需要明确“当前函数返回什么、上一层拿到什么”。二叉树题目主要是层序遍历、最近公共祖先、路径总和这几类。层序遍历考察队列的使用最近公共祖先则考察递归的返回值设计。这里有一个常用技巧递归函数如果返回null表示“没找到目标节点”返回非null表示“找到了某个目标节点或本身就是答案”。这个套路理解了很多二叉树题都能套。动态规划考过经典的“最长递增子序列”和“背包问题变形”。很多同学怕DP其实校招笔试的DP题很少出到需要状态压缩或斜率优化这种高级技巧基本上只要你能写出正确的状态定义和转移方程哪怕复杂度不是最优也能拿到大部分分数。关键是别慌先把“dp[i]代表什么”写清楚。字符串处理我遇到的是大数相加和最长回文子串。大数相加考的是模拟竖式运算注意进位处理和两个数字长度不一致的情况最长回文子串用中心扩展法是最好理解的遍历每个字符分别按奇数长度和偶数长度向两边扩散即可。这个思路比动态规划更直观也更容易在笔试紧张状态下写对。注意校招笔试的算法题代码能跑通只是最低要求。如果你能在注释里写明时间复杂度和空间复杂度或者在代码里处理掉几个明显的边界case面试官对你的评价会明显上一个档次。这是我在后来的面试中亲测有效的细节。2.2 Java基础与JVM后端候选人绕不开的“内功心法”蘑菇街后端以Java技术栈为主所以Java基础在笔试中的占比相当高。这块考察不是简单的语法题而是集合源码、并发工具和JVM原理的深度理解。集合类里HashMap和ConcurrentHashMap是重点中的重点。HashMap要能说清楚put流程先计算key的hash值再通过扰动函数降低碰撞概率然后定位到桶数组下标当链表长度超过阈值默认8且数组长度大于64时链表转红黑树扩容时是2倍扩容元素要么在原位置要么在原位置加旧容量的偏移量。ConcurrentHashMap在JDK 8后放弃了分段锁改用CAS synchronized锁桶头节点锁粒度更细、并发度更高。笔试如果出这类题往往还会追问“为什么链表长度阈值是8”这其实是基于泊松分布的统计结果在负载因子0.75的前提下链表长度达到8的概率已经极低。并发编程考过synchronized和ReentrantLock的区别、volatile的可见性原理、线程池的核心参数含义。这里有个高频考点线程池的拒绝策略。AbortPolicy直接抛异常、CallerRunsPolicy让提交任务的线程自己跑、DiscardPolicy静默丢弃、DiscardOldestPolicy丢弃队列里最老的任务。业务上常见的是CallerRunsPolicy因为它能起到天然限流的作用让生产者放慢速度。我当年笔试就有一道题是“线程池队列满了新任务怎么办”其实就是考察这几种策略的适用场景。JVM这块内存区域划分和垃圾回收算法是常客。堆、虚拟机栈、本地方法栈、方法区、程序计数器每块区域存什么、会不会OOM、需不需要GC都要能说清楚。垃圾回收算法里标记-清除、标记-复制、标记-整理这三种要对比记忆还要知道分代收集理论新生代对象朝生夕灭用复制算法最划算老年代对象存活率高用标记-整理或标记-清除更合适。CMS和G1的区别也是高频题G1用Region划分堆、可预测停顿时间适合大堆场景。注意JVM题最忌讳只背结论不背原理。比如“为什么老年代用标记-整理”如果你能补充一句“因为老年代对象存活率高复制算法会产生大量复制开销而标记-清除会产生内存碎片所以选标记-整理”面试官就知道你是真的理解而不是背了八股文。2.3 计算机网络与操作系统理论是底线但不是背题就行计算机网络和操作系统这两门课很多同学觉得就是背。笔试确实有很多背诵型考点比如TCP三次握手、四次挥手、HTTP状态码含义、进程和线程的区别。但蘑菇街这份笔试题里有几道题特别强调“应用场景”也就是告诉你一个现象让你用网络或系统原理去解释。TCP三次握手考试不只是要求画出时序图还会追问“为什么是三次而不是两次或四次”。答案核心在于三次握手能确保通信双方都确认自己和对方的收发能力正常同时能同步初始序列号。两次握手可能导致已失效的连接请求突然到达服务端让服务端误以为是新连接白白建立一条空连接。我建议大家把这个原理用自己的话讲一遍能讲顺了才是真正掌握。HTTP和HTTPS的区别也是必考。HTTP是明文传输HTTPS在HTTP和TCP之间加了一层TLS/SSL加密。TLS握手流程比TCP握手复杂得多客户端先发ClientHello服务端回ServerHello和证书客户端验证证书后生成预主密钥用服务端公钥加密发给服务端双方再用预主密钥生成会话密钥之后切换到加密通信。笔试常见的考点是“HTTPS为什么比HTTP慢”答案是多了一次TLS握手耗时以及加密解密的CPU开销。操作系统主要考进程线程区别、死锁的四个必要条件、虚拟内存和页面置换算法。死锁的四个条件要背互斥、持有并等待、不可剥夺、循环等待。但更好的答法是连“如何破除”一起说比如用资源有序分配法破除循环等待用一次性申请所有资源破除持有并等待。这样显得你不仅有知识还有解决问题的意识。还有一个高频题进程和线程的区别。标准答法是进程是资源分配的最小单位线程是CPU调度的最小单位同进程的线程共享地址空间、打开的文件等资源但线程有自己独立的栈和寄存器上下文进程切换开销大于线程切换。如果你能补一句“在后端高并发场景中线程模型更轻量所以Java用线程池来处理请求”这就把理论连接到工程了。3. 从真题到实操几个典型题目的完整复盘3.1 手写题单链表反转的三种实现与复杂度分析单链表反转是校招笔试出现频率极高的手写题蘑菇街也考了。我复盘一下完整的解题路径。方法一迭代法。这是最应该掌握的写法。定义三个指针pre初始为nullcur指向头节点每次循环里先保存cur.next然后把cur.next指向pre接着pre和cur各自前移一位。循环结束后pre就是新链表的头节点。时间复杂度O(n)空间复杂度O(1)。public ListNode reverseList(ListNode head) { ListNode pre null; ListNode cur head; while (cur ! null) { ListNode next cur.next; cur.next pre; pre cur; cur next; } return pre; }方法二递归法。递归的关键是明确函数语义reverseList(node)返回的是以node为起点的链表反转后的新头节点。假设node.next之后的子链表已经反转好了那么只需要让node.next.next指向node再让node.next指向null即可。public ListNode reverseList(ListNode head) { if (head null || head.next null) { return head; } ListNode newHead reverseList(head.next); head.next.next head; head.next null; return newHead; }方法三头插法。新建一个虚拟头节点dummy遍历原链表时把每个节点摘下来插到dummy的后面最后返回dummy.next。这个方法逻辑上很直观而且能自然扩展到“按K个一组反转链表”的题目。我在实际笔试和面试中见过不少人栽在递归法上。最大的坑就是没想清楚“head.next.next head”这行代码到底在干什么。建议写递归前先在纸上画三个节点的链表把递归展开一层看。画完哪怕代码记不全也能现场推导出来。注意这类手写题笔试环境下的编码规范性非常重要。方法名、变量名要有意义不要写a、b、c这种。另外如果笔试平台是白板编程代码缩进和括号对齐一定要清晰否则阅卷体验很差。3.2 数据库题为什么索引要选B树而不是哈希或二叉树数据库部分蘑菇街笔试有一道让我印象深刻的题MySQL的InnoDB引擎为什么用B树做索引结构而不是哈希表或二叉查找树。这题看似基础但真能答好的不多。先对比一下各个数据结构的优劣。哈希表等值查询效率确实最高O(1)就能找到。但哈希索引不支持范围查询比如“WHERE price 100”这种哈希表只能全表扫。另外哈希索引无法利用索引排序ORDER BY也没法加速。所以哈希索引适合记忆场景等值查询为主比如Redis里的Hash类型但它不适合关系型数据库的通用索引。二叉查找树理想情况下查询复杂度是O(log n)但插入数据是递增序列时树会退化成链表查询复杂度变成O(n)。平衡二叉树AVL能解决退化问题但每次插入删除都可能触发旋转操作节点多的时候树高度依然偏高而且每个节点只存一个数据磁盘IO次数多。B树所有数据都存储在叶子节点并且叶子节点之间用指针连接成一个有序链表。这意味着范围查询非常高效只要找到下界顺着链表往后遍历就行。同时非叶子节点只存索引键不存数据所以每个磁盘页能容纳的索引键数量比B树更多树高更低查询路径上的磁盘IO次数更少。典型的3~4层B树就能支撑千万级数据量。这道题之所以经典是因为它把“数据结构”和“磁盘存储特性”结合了起来。如果只答“B树查询快”是不够的一定要提到InnoDB默认数据页大小是16KB磁盘IO是按页读取的树高决定了查询的IO次数。树越低IO越少查询越快。另外这道题还经常延伸出另一个问题为什么InnoDB主键推荐用自增整数而不是UUID。答案是因为B树的叶子节点是有序的自增主键插入时只需要追加写不容易触发页分裂UUID是随机的插入时可能插入到已有页的中间位置频繁触发页分裂和重排产生大量碎片性能会明显下降。注意答数据库题一定要区分“InnoDB特性”和“MySQL通用特性”。比如MyISAM也支持索引但它不支持事务、外键、行级锁索引文件和数据文件也是分开的。说“MySQL用B树”时更准确的说法是“InnoDB存储引擎用B树”。3.3 场景题设计一个支持高并发的下单接口如何下手场景设计题是蘑菇街这类电商公司笔试题的压轴常客。我遇到的题目大意是大促期间瞬时下单量是平时的几十倍设计一个下单接口要求不能超卖同时要保证接口可用性。这类题没有标准答案但考察的是你有没有一套成熟的“高并发设计方法论”。我的答题结构大致如下你可以直接参考这个框架。第一步分析瓶颈。下单链路中核心的瓶颈是数据库。每次下单都要扣减库存、写入订单如果所有请求直接打到数据库数据库的连接数和行锁竞争会瞬间击穿。所以核心思路是“层层削峰”让数据库只处理真正有效的请求。第二步加入缓存。商品库存预热到Redis用Redis的原子操作扣减库存。这里要特别注意Redis扣库存必须用Lua脚本保证原子性不能用“先get再set”这种非原子操作否则并发下必然超卖。Lua脚本里可以做库存大于0的判断然后执行decr操作再把结果返回。这样Redis单实例就能扛住每秒几万次的扣减请求。第三步削峰填谷。用户点击下单后不直接同步扣库存返回结果。可以先把订单请求写入消息队列下单服务异步消费队列真正落库时再校验一次库存并生成订单。这样即使瞬间有10万请求数据库在这一秒可能只接收几百个真实业务请求压力完全不同。第四步接口防重。用户手抖点了两次提交不能生成两笔订单。前端可以禁用按钮但后端必须做幂等。常见的方案是生成一个全局唯一请求ID下单时带上服务端用Redis记录这个ID是否处理过处理过就返回第一次的结果。第五步兜底方案。即使Redis挂了也不能让下单链路全挂。可以把库存预热的操作做成可降级的Redis不可用时直接查数据库库存走同步扣减虽然性能差但至少保底。同时用限流组件如Sentinel或自研计数器拦截超量请求返回“系统繁忙”而不是让请求全量打到后端。这套思路回答下来面试官能清楚看到你有没有“流量治理”的概念。哪怕你中间有些细节不完善也比只回答“加锁、同步扣库存”要完整得多。这也是我后来参与校招面试时最看重的素质遇到复杂问题能不能分步骤、分层次地给出解决方案。4. 那些年我们踩过的坑笔试失分点和备考建议4.1 常见失分点速查与避坑指南我整理了一份实战中非常容易出错的失分点对照表每一条都是我和身边同学真实踩过的坑。失分点典型表现正确做法算法题边界条件漏判链表反转时没处理空链表写代码前先枚举边界空、单节点、双节点HashMap原理背串把JDK 7的链表头插法说成JDK 8分清版本差异JDK 8是尾插法线程池参数记混把maximumPoolSize和corePoolSize搞反结合执行流程记忆先核心、再队列、再最大、最后拒绝TCP挥手次数记错回答成三次挥手熟记TIME_WAIT状态存在的原因自然记住是四次索引题答得太浅只答“B树查询快”引入磁盘IO、页大小、树高、范围查询来展开场景题不分层一上来就说“加锁”使用缓存、队列、幂等、限流的分层思路时间分配失衡算法题花太久导致数据库大题没时间写先通读全卷按分值分配时间时间分配这个坑我当年就吃过亏。笔试题量不小如果前面算法题卡太久后面的大分值场景题很容易草草收场。建议拿到试卷先花两分钟通读一遍把会做的题先划出来优先保证分值高的题目能写完。算法题如果五分钟没思路先跳到下一题回头再补。另外还有一个容易被忽略的点写SQL题时一定要看清表结构和题目要求的输出字段。很多人SQL语法写对了但少了一个GROUP BY字段或者JOIN条件写错整题没分。写完再用题目给的例子自查一遍这种低级失误完全能避免。4.2 校招后端笔试的备考路线按4到8周怎么排我知道很多准备校招的同学最迷茫的不是“学什么”而是“按什么顺序学、每个阶段学多久”。结合自己当年从零开始准备的经验我整理了一个4到8周的可执行方案。第一阶段基础补全与算法热身第1到2周。先把计算机网络、操作系统、数据库三本核心教材的常考章节过一遍不追求精读关键是建立知识框架。算法部分每天保持2到3道LeetCode题先从链表、栈、队列、二叉树这些基础数据结构刷起。这个阶段目标是能独立写出迭代版链表反转、二叉树层序遍历这类题。第二阶段Java深度专题第3到4周。系统看HashMap、ConcurrentHashMap的源码整理线程池、锁、JVM内存模型的笔记。每天做1道并发编程相关的题可以是手写单例、手写阻塞队列这类经典实现。如果英语阅读没问题强烈建议翻一翻官方文档和源码注释比任何二手资料都准确。第三阶段中间件与场景题专项第5到6周。重点理解Redis的数据结构和持久化机制、消息队列的基本模型、分布式锁的常见实现方式。场景题每天练1道可以从“短链系统设计”“秒杀系统设计”“购物车设计”这几个经典题目入手每道题都按“分析瓶颈、分层设计、落地细节”的框架写。这个阶段对你架构思维的提升非常明显。第四阶段真题模拟与查漏补缺第7到8周。找近两年的校招后端笔试题按真实考试时间模拟。做完后认真复盘错题归因是知识点没掌握还是审题不清还是时间分配问题。最后一周把高频考点快速过一遍尤其是有手写代码要求的题一定要亲手敲一遍不能只“看会了”。这个方案看着内容多但每阶段下来会觉得特别踏实。我当时就是按照类似节奏准备的从“看到线程池参数就犯怵”到“送分题稳拿”差不多用了6周时间。写在最后重新回顾蘑菇街2019届校招这套后端笔试题我觉得它给我最大的启发是后端笔试不是知识竞赛而是能力体检。它希望你展示的不是“背了多少八股”而是“能不能用基本原理解决工程问题”。我至今还记得笔试最后一道场景题上我写下了“缓存、队列、幂等、限流”四个关键词虽然当时的理解还很稚嫩但正是这种结构化思考的方式让我在后续的面试里越来越从容。如果你正在准备校招后端岗位有几句话送给你第一基础不牢地动山摇计算机网络、操作系统、数据库这些底层知识一定要踏踏实实过一遍第二代码题一定要亲手写想和写之间隔着一道鸿沟第三准备几套属于自己的场景题答题框架遇到没见过的题也能稳住阵脚。还有就是笔试只是起点过了笔试还有面试。但每一次笔试都是一次实战演练考完无论结果如何花点时间复盘错题把不会的搞懂这才是这份试卷真正留给你的价值。