ARTICLE DETAIL

资讯详情

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

2024秋招淘天Java后端笔试实录:算法、并发与秒杀系统设计全复盘

2024秋招淘天Java后端笔试实录:算法、并发与秒杀系统设计全复盘 8月中下旬2024年秋招的第一波笔试高峰来了。我投的是阿里巴巴淘天集团的工程岗网申提交后大概一周邮件和短信同时收到“笔试邀请”批次被排在了第一批。说实话收到通知那一刻是既兴奋又紧张——淘天是电商领域的技术重镇能用Java写高并发场景的代码是很多后端候选人的理想去处。整场笔试两个小时平台用的牛客网考点覆盖单选、多选、编程题和一道设计类问答难度梯度拉得很开。这两天把考过的题、踩过的坑以及考后复盘重新理了一遍用这篇实录完整记录下来给后面批次的同学一个参考。1. 笔试基本信息与考试环境先弄清楚“和谁打、规则是什么”1.1 从投简历到收到笔试通知的时间线2024年秋招和往年相比节奏明显提前了。淘天集团大概在8月初放出了工程岗的校招投递入口我当时通过官网校招系统投递了简历选择的是后端开发方向。本来以为会等很久没想到一周左右就收到了笔试通知。邮件内容很直白笔试平台、考试时间、考试时长、硬件要求全部列清楚。这里有两个容易被忽略的细节。第一邮件里会要求提前调试好摄像头和麦克风正式考试时会有“屏幕录制摄像头抓拍切屏检测”三重监控页面一旦离开考试窗口超过一定次数会记录异常甚至直接交卷。第二淘天的笔试通知通常分批次发放同样一个岗位先投递的人往往被安排在更早的批次所以“先投先面、先投先考”在秋招里是真实存在的不要卡着DDL才投。笔试时间全程120分钟选择的是工作日晚上19点到21点。这个时间段安排其实挺友好不和学校课程冲突也方便已经实习的同学下了班再考。不过我自己的体会是晚上考精力消耗比白天大如果当天白天还要实习开考前的半小时最好留出来休息别让脑子带着白天的疲惫上阵。1.2 牛客考试平台的使用细节淘天这批笔试用的牛客网编程题是“ACM模式”也就是需要自己处理标准输入输出而不是刷LeetCode那种核心代码模式。这个差异对很多刚接触校招笔试的同学来说是最容易翻车的地方。你在LeetCode上写完一个函数就完事了但在这里必须从public static void main开始写自己Scanner读数据自己System.out.println输出结果。考试页面左侧是题干右侧是代码编辑区。编辑器支持代码补全和语法高亮但版本不一定是你平时用的那个Java一般是JDK 8或JDK 11C是GCC系列Python可能是3.8左右。提交之后系统会跑若干隐藏测试用例只告诉你通过率不告诉你具体哪一个没跑过。平台的自测功能可以做“本地样例测试”但自测只能验证你输入的一段样例过了不代表隐藏用例能过。我考场上有一次自测输出完全正确结果提交只有80%通过率排查下来是极端case里的int溢出了。所以平时练习就要养成用大数、边界数据测试的习惯。另外提醒一点牛客的选择题模块是独立的进入选择题之后可以切到编程题但选择题一旦切换到下一个题就不能回头修改。我的策略是先把拿不准的选择题当作“未定”记在草稿纸上等所有题目做完再回来看结果发现系统不允许回看白白浪费了对答案的时间。后来才意识到应该在开考后先把选择题做完并直接确认再留出整块时间做编程题。1.3 题型构成与分值分布淘天这批笔试的题型比较固定单选题、多选题、编程题、设计问答。不同批次的题量和分值会有浮动但整体框架大差不差。题型大概题量单题分值建议用时单选题10~15题2分15分钟多选题5题左右2分5分钟编程题3题20~30分80分钟设计问答1题20分15分钟从分值结构就能看出来编程题是决定性的大头三道加起来超过一半。很多人说“笔试主要看选择题能不能及格”这话不完全对。我身边有同学选择题几乎满分但三道编程只写出一道最终还是没能进面。反之编程题AC两道、选择题中等偏下的几乎都收到了后续流程的通知。还有一个容易被低估的部分是设计问答。它不给标准答案只看你能否把思路表述清楚、有没有工程常识。这题我放在最后做虽然时间不够文字写得很满但框架和关键词都给了后面也会详细说我是怎么组织答案的。2. 编程题复盘电商场景里的算法题长这样淘天笔试的编程题有一个很明显的特征题目喜欢包裹电商业务场景但内核依然是经典算法。读题的时候不要被一长串业务背景吓到剥掉外壳之后往往是你熟悉的模型。下面几道题是基于我和同批次同学考后的复盘整理题目描述经过转述核心考点保持不变。2.1 第一题优惠券可用天数区间合并题目大意是平台给用户发了很多优惠券每张券有生效日期和失效日期。给定某用户所有优惠券的起止日期求该用户至少拥有一张可用优惠券的总天数。日期按天计区间是左闭右开即[L, R)表示从L日开始、R日之前有效。数据量大概在10^5级别日期范围可能跨年。这个题就是标准的区间合并。难点在于识别出“区间并集长度”这个考点而不是被“优惠券”三个字带偏。做法是先把所有区间按左端点排序维护当前覆盖到的最右端curR。遍历时如果新区间的左端点不超过curR说明两个区间有重叠更新curR为两者的较大值否则把当前累积的长度加到答案里然后开启新的一段区间。import java.util.*; public class Main { public static void main(String[] args) { Scanner in new Scanner(System.in); int n in.nextInt(); long[][] segs new long[n][2]; for (int i 0; i n; i) { segs[i][0] in.nextLong(); segs[i][1] in.nextLong(); } Arrays.sort(segs, (a, b) - Long.compare(a[0], b[0])); long ans 0; long curL segs[0][0], curR segs[0][1]; for (int i 1; i n; i) { if (segs[i][0] curR) { curR Math.max(curR, segs[i][1]); } else { ans curR - curL; curL segs[i][0]; curR segs[i][1]; } } ans curR - curL; System.out.println(ans); } }这个题有两个关键坑。第一个是数据类型日期跨度如果从1月1日到12月31日再加上跨年场景int很容易溢出我一上来就用long处理。第二个是区间右端点的开闭问题题目明确是左闭右开那么长度就是curR - curL不需要加1。不少人在这个1/-1上翻车样例给了个普通情况隐藏用例覆盖了跨年和单日券的边界一不注意就少算多算。2.2 第二题直播间秒杀商品分配二分图最大匹配这题的面目是一段“直播抢购”设定一个直播间有若干种商品每种商品有固定库存有很多用户同时参与秒杀每个用户最多可以买一件商品并且每个用户有一个“心愿商品列表”列表长度很小不会超过3个。问最多能让多少用户买到心仪商品。题目读完第一反应是贪心按用户心愿列表长度排序短的用户优先分配分配时优先选择库存多的商品。现场我先写了这个贪心也能过一部分用例但提交后通过率卡在了某个百分比上说明存在反例。贪心在这里不是稳的因为商品的库存数量是“多人共享”的这个决策会影响后面的人。真正稳妥的做法是把问题转成二分图最大匹配。左边是用户右边是“商品库存展开后的每个单位”比如商品A有3件就拆成3个槽位每个用户只能连到自己心愿商品对应的槽位。匹配数就是最多能买到的人数。这个模型虽然拆出来的点可能比较多但总边数等于“用户数×3”规模完全可控。// 伪代码核心匹配逻辑 boolean hungary(int u, boolean[] vis, int[] match, ListInteger[] g) { for (int v : g[u]) { if (vis[v]) continue; vis[v] true; if (match[v] -1 || hungary(match[v], vis, match, g)) { match[v] u; return true; } } return false; } // 遍历每个用户重置vis数组调用hungary匹配成功则ans这里我特别想强调笔试现场遇到“看似贪心、实则图论”的题目不要死磕贪心证明。能用最大流或匈牙利算法就跑标准算法虽然实现更复杂但正确性有保障。尤其是这种“每人选一个、资源有上限、求最大满足人数”的模型几乎是二分图匹配的招牌场景。考场上我先写了贪心拿部分分然后补上匈牙利算法最后两版对比验证确认匈牙利版能覆盖所有自测用例才提交。2.3 第三题物流路径的单段最大成本最小化二分答案最短路这道题放在最后难度明显上一个台阶。题面大概是有n个仓库m条有向道路每条道路有一个运输成本现在需要把一批货从仓库s运到仓库t要求全程总运输成本不超过一个给定上限L求方案中“单段道路最大成本”的最小可能值。看到“最大值最小”这种字眼第一反应就应该是二分答案。我们二分一个x判断只允许走成本不超过x的道路时从s到t是否存在一条总成本不超过L的路径。这里需要注意单段限制和总成本限制是两个约束不能只判连通性还要在最短路算法里把总成本算出来。实现上每次check(x)时对每个点跑Dijkstra跳过边权大于x的边看dist[t]是否不超过L。二分范围是[1, 单段最大成本]复杂度是O((m log n) log W)在n10^5级别也可以接受。boolean check(int x, Listint[][] allEdges, int n, int s, int t, long L) { Listint[][] g new List[n]; for (int i 0; i n; i) g[i] new ArrayList(); for (int u 0; u n; u) { for (int[] e : allEdges[u]) { if (e[1] x) g[u].add(e); } } long[] dist new long[n]; Arrays.fill(dist, Long.MAX_VALUE); dist[s] 0; PriorityQueuelong[] pq new PriorityQueue((a, b) - Long.compare(a[1], b[1])); pq.offer(new long[]{s, 0}); while (!pq.isEmpty()) { long[] cur pq.poll(); int u (int) cur[0]; long d cur[1]; if (d dist[u]) continue; for (int[] e : g[u]) { int v e[0]; long nd d e[1]; if (nd dist[v]) { dist[v] nd; pq.offer(new long[]{v, nd}); } } } return dist[t] L; }这道题真正难的不是算法本身而是能不能在考场高压下快速想到“二分答案”这个方向。很多人在读题后直接开始写Dijkstra把单段最大成本作为一个约束条件塞进去结果怎么写都不对。我的经验是看到“最大中取最小”“最小中取最大”这类表述先把二分答案作为优先选项然后思考“如果答案给定为x怎么快速验证可行性”。这个套路在笔试里出现频率非常高值得专门训练。2.4 同批次额外听到的一道DP题后面和同批次考生交流时听到一套平行卷里有一道动态规划题目大概是在订单价格序列中要求最多完成两笔不重叠的交易求最大收益。这题其实是经典股票问题的变种。思路可以用前后缀分解定义pre[i]为第0天到第i天完成一笔交易的最大利润suf[i]为第i天到最后一天完成一笔交易的最大利润。那么答案就是枚举分割点求pre[i] suf[i1]的最大值。我简单模拟了一下int[] pre new int[n]; int minP prices[0]; for (int i 1; i n; i) { minP Math.min(minP, prices[i]); pre[i] Math.max(pre[i - 1], prices[i] - minP); } int[] suf new int[n]; int maxP prices[n - 1]; for (int i n - 2; i 0; i--) { maxP Math.max(maxP, prices[i]); suf[i] Math.max(suf[i 1], maxP - prices[i]); } int ans 0; for (int i 0; i n - 1; i) { ans Math.max(ans, pre[i] suf[i 1]); }这类题的本质是“枚举一个切分点让前后两个子问题独立”。很多区间型、序列型DP都可以往这个方向想。淘天笔试喜欢把算法题和电商业务绑定但业务包装背后的考察点还是这些基本功所以刷题时不要只追求AC数量多总结题目背后的模型。3. 选择题和问答比“背八股”多了一步“会判断”淘天笔试的选择题覆盖面很广Java、并发、数据库、Redis、操作系统、计网都有涉及。题目本身不算偏但选项设计得有迷惑性不是靠死记硬背能轻松拿分的。3.1 Java并发与容器高频题考到了ConcurrentHashMap的实现细节。选项包括读操作是否加锁、size()是否精确、put是否需要锁整个哈希槽、扩容时是否支持多线程协助。正确答案是读操作不加锁、size()是基于baseCount和CounterCell累加的近似值、put时通过CAS加锁单个槽位、扩容时多个线程可以协助迁移数据。这个点如果只背“线程安全”四个字肯定分不出来必须真正理解CAS和volatile的配合机制。另外一个高频考点是synchronized和ReentrantLock的区别以及“锁升级”的概念。偏向锁、轻量级锁、重量级锁这些名词很容易记混。我提供一个记忆锚点锁升级的触发前提是“竞争加剧”无竞争的时候用偏向锁出现少量竞争用CAS自旋自旋失败再膨胀为重量级锁。笔试选项经常把“自旋发生在重量级锁阶段”作为错误项混进去抓住“自旋就是轻量级的CAS尝试”就不会错了。3.2 操作系统与计网的基础判断操作系统考了页面置换算法。题目给了一个访问序列问使用LRU时缺页次数是多少。这种题只要画一个简单的队列就能算出来但要注意初始内存为空和元素重复访问两种情况。我记得第一轮算出来是缺页7次结果选项里没有重新读题才发现题目问的是“将页面装入内存后第二次访问是否缺页”的计算口径最后在缺页数上加了个初始化值才对上。计网部分考了TCP拥塞控制。我印象最深的一题当发生超时重传时cwnd和ssthresh分别如何变化。正确行为是ssthresh降为当前cwnd的一半cwnd直接降为1并重新进入慢启动。而收到3个重复ACK触发快速重传时cwnd减半进入拥塞避免而不是归1。这两个场景一旦混淆选项就全错。这里还有一个心得考试前把TCP拥塞控制的四个阶段慢启动、拥塞避免、快速重传、快速恢复用一张图过一遍基本能覆盖这类题的考点。3.3 MySQL和Redis的经典选择题数据库考了联合索引的失效条件。题目给了一个联合索引(a, b, c)问哪些查询能用到索引。正确项包括a1 and b2 and c3、a1 and b2、a1 order by b错误项包括b2 and c3以及a1 and b0 and c3范围查询后面的c无法用于索引排序。这里的关键是“最左前缀法则”和“范围列后面的列无法使用索引”这两个规则理解了原理就不容易被选项绕进去。Redis部分主要考察缓存问题缓存穿透、缓存击穿、缓存雪崩的定义和应对方式。穿透是指查一个压根不存在的key每个请求都直接打到数据库可以用布隆过滤器拦截击穿是指一个热key过期瞬间大量请求打到DB可以用互斥锁或逻辑过期雪崩是指大批key同时过期或Redis宕机解决思路是过期时间加随机值、多级缓存、限流降级。三个概念名字很像但成因和应对完全不同建议画一张对比表来区分。3.4 最后那道设计问答秒杀系统库存扣减方案设计问答的题面大意是某平台要做大促秒杀活动商品库存有限会有大量用户同时抢购请设计一套库存扣减方案要求防止超卖、保证性能、并说明为什么这么做。这道题不需要写代码但要在答题框里把思路组织得有层次。我用的是“漏斗式”答题结构一层层往下写。第一层是接入层的防护Nginx配合Lua脚本做按用户维度的限流防止单个用户反复请求同时用CDN静态化商品详情页尽量把流量拦截在贴近用户的网络节点。第二层是服务层请求通过网关进入秒杀服务后先走Redis的DECR原子操作预扣库存用一个很小的Lua脚本把“库存判断扣减”合并为一个原子步骤避免并发扣超。第三层才是数据库只有Redis扣减成功的请求才能进入MQ异步创建订单数据库用一个update stock set stock stock - 1 where id ? and stock 0做最终扣减保住库存的底线。最后补充了数据一致性方案比如对账补偿、状态机标记、失败订单回补库存。这个结构不是标准答案但胜在“分层清晰、每一步都有理由”。我特意在每一层都标注了这个设计要解决的问题比如“为什么用Redis预扣而不是直接打DB”答案是数据库的TPS撑不住秒杀峰值Redis的原子操作可以在毫秒级完成库存判断。这种“先讲做法、再讲原因”的表述方式在问答和面试里比单纯堆概念更容易拿分。4. 两小时怎么分配做题顺序和临场取舍笔试不止考你会不会还考你两个小时里能不能把会做的都拿到分。这个题量设计是故意多过正常完成量的能全部做完且做对的人极少所以“取舍”本身就是考察能力的一部分。4.1 我采用的时间分配开场前5分钟我先把整张卷子从头到尾扫一遍。主要干三件事确认编程题有几道、每道题的数据范围大概是多少、设计问答的题干要求是什么。不要小看这5分钟它能让你在心理上对题目难度有预期避免从最难的一题开始导致时间失控。接下来直接做选择题。我的策略是“不回头、不纠结”。会做的题凭第一直觉选拿不准的用排除法遇到明显不会的先随机选一个然后立刻跳到下一题。选择题总共控制在20分钟以内不要在上面花超过25分钟。原因很简单一道选择题只有2分而一道编程题可能有30分把时间花在纠结选择题上是最不划算的买卖。编程题我按“先易后难”的顺序做。第一题区间合并是最简单的大概10分钟就AC了。第二题匈牙利算法实现起来比较繁琐我一边写一边测试花了35分钟。第三题二分答案Dijkstra识破了模型但写代码时在check函数里构建临时图的结构上浪费了一些时间大约用了30分钟。整体编程题用了75分钟左右留下15分钟给设计问答。虽然设计问答时间偏紧但框架和关键词都写上去了最后也顺利进入下一个流程。4.2 现场突发情况的应对考试中我遇到两个突发情况。第一个是IDE的代码补全非常弱ArrayList的add方法都要手敲导致写代码速度明显变慢。解决方法是平时练习时尽量不要依赖自动补全把常用集合类的方法名、参数记在肌肉记忆里。笔试现场的编辑器版本五花八门多备份手动写代码的能力。第二个是选择题页面切换到编程题之后系统提示“当前模块已提交无法返回”。当时我正准备回头检查一道不确定的多选题直接被拦住了。后来才知道牛客这类平台里选择题模块提交后就是最终状态。所以最佳方案是先花20分钟把选择做完并确认不要想着回头再改。如果担心自己第一直觉不准最多在草稿纸上给自己留一个“待定”清单但还是要提前做好心理准备定了就不改。4.3 ACM模式下调试的细节牛客的ACM模式还有一个隐蔽坑输入可能有多个测试文件或者一行数据有多余空格。自己写Scanner时尽量用next()和nextInt()而不是nextLine()处理因为nextLine()容易受换行符影响导致读到空串。读取整数时如果遇到一行的数据数量不确定建议直接用while (in.hasNext())循环处理。还有一个性能教训不要把System.out.println放在内层循环里否则大数据量时输出会成为性能瓶颈。我有一次本地测试通过了提交后运行时间偏长检查发现是因为每个匹配结果都立刻打印出来。正确的做法是把结果累积到StringBuilder最后一次性输出。这些细节看起来不起眼但在“隐藏用例数据量很大”的笔试场景里可能直接影响你是否能AC。5. 笔试后三天复盘、补弱和备战面试笔试结束不等于流程结束。从我自己的经验来看考后的复盘和考前的准备同样重要它既决定了你能不能真正从一次笔试里学到东西也可能影响后续面试表现。5.1 考后当天立刻记录题目和思路人脑的记忆曲线很残酷考完当天不把题目记下来三天后就会忘得差不多。我习惯在笔试结束后马上打开备忘录把每道题的题干关键点、我的解法、卡住的地方、最终通过率全部写下来。尤其是那些“没做出来但知道思路”的题更要写清楚当时的瓶颈是什么。这次的记录我写了大概一千字包括三道编程题的题目抽象、区间合并的边界条件、匈牙利算法的实现细节、二分答案最短路的结构以及设计问答的分层方案。不到一周后的面试里有些问题直接和笔试内容挂钩这些复盘笔记帮我快速回忆起了当时的思考过程回答的时候明显比临时想更有底气。所以别觉得笔试考完就翻篇了面试官很喜欢拿着你的笔试记录追问细节。5.2 针对弱项延伸刷题复盘后我把自己的薄弱点定位在“图论模型的转化”和“二分答案的check函数设计”上。之前刷题刷得多的是数组、哈希、双指针、动态规划对图论题存在回避心理。既然笔试被这个点卡过一次接下来一周我集中刷了三类题二分答案最大化最小、最小化最大、最短路变种分层图、限制条件、二分图匹配匈牙利算法、最大流基础。刷的时候也不是盲目刷数量而是每题都问自己三个问题题目在考什么模型数据范围多大这个模型对应什么样的经典解法比如看到“n个人m个物品每人只能选一个物品有容量”就条件反射想到二分图匹配看到“最大值最小”就条件反射想到二分答案。这种条件反射不是背题背出来的是从几十道同类型题目里总结出来的识别特征。5.3 与面试的衔接笔试到约面笔试结束大概三天后我收到了面试邀约。淘天的流程推进速度比较快一面主要围绕项目和Java基础展开也追加了一道手撕算法题。那道题恰好和笔试的股票交易变种很相似只是从“最多两次交易”改成了“最多k次交易”。因为笔试复盘时我把前后缀DP的思路彻底想透了现场很自然就推出了dp[i][j]表示“第j天完成i笔交易的最大收益”的动态规划写得比笔试时还顺畅。这件事给我最大的启发是笔试题不是孤立的它往往代表了面试官认为“一个合格候选人应该掌握”的知识点。你把笔试题吃透相当于提前预演了面试的一部分内容。所以如果某道笔试题目没做出来千万别自我安慰“反正考完了”把它当作一个免费的考点雷达及时补上才是聪明人的做法。5.4 给下一批次同学的建议如果让我给还没参加笔试的同学一句总结性建议那就是把刷题重心从“会做经典题”转移到“能在两个小时内稳定拿到中上分数”。淘天笔试的题目未必比LeetCode Hard难但组合在一起加上时间和心理压力考察的是综合应对能力。具体来说第一提前锁定一两道保分题。区间合并、前缀和、排序扫描这类“读题就能想到思路”的题目必须在15分钟内保证AC这是心态的定海神针。第二把常见算法模板背到条件反射级别尤其是二分答案、Dijkstra、并查集、最近公共祖先、拓扑排序这些高频模型考场上是没有时间现推模板的。第三做一套完整的限时模拟严格按照两个小时、真实平台、不开本地IDE的模式来一遍提前适应“看题-写码-自测-提交”的完整节奏。我实际参与下来最大的感受是淘天的笔试没有特别偏门怪题但每道题都要求你在限定时间内把已经掌握的知识准确输出。这种能力不靠考前突击靠的是日常刷题时反复模拟“真实考场”的思维习惯。希望这篇实录能给后面参加笔试的同学一些具体帮助祝你们都能拿到心仪的面试机会。最后再分享一个小技巧。笔试开始后的前5分钟通读全卷时我会顺手在草稿纸上写下三个编程题的“题目模型关键词”比如“区间合并”“二分图匹配”“二分答案Dijkstra”。写完之后再去写代码思路会清晰很多因为大脑已经提前给每一题定了方向不用边写边想“我到底该用哪个算法”。这个方法帮我避免了很多次“写到一半发现方向错了”的惨案后面批次的同学可以试试。
返回列表