面灵AI→

美团小象超市后端一面:项目连环追问与 K 个一组翻转链表

轮次
一面
时间
2026-09
来源
牛客网

《面试题目》

  1. 请做一下自我介绍。
  2. 你现在是在腾讯实习吗?未来的 offer 怎么选?
  3. 为什么你这个专业要找互联网工作?
  4. 项目里的 CSS 匹配缓存是怎么设计的?
  5. 什么是 Copy-On-Write 操作?
  6. AI 项目的高并发是怎么做的?
  7. 线程池设计需要注意什么?
  8. 什么时候用锁?什么时候不用?
  9. 原子变量是什么?
  10. 数据库表结构怎么设计?
  11. 如何保证消息顺序?
  12. 数据量大怎么办?
  13. 索引什么时候会失效?
  14. 为什么要用 MQ?
  15. 如何保证消息不丢?
  16. MVCC 原理是什么?
  17. 手撕代码:K 个一组翻转链表。
  18. 是否接受北京/上海?
  19. 是否接受写 Java?
  20. 对 AI 焦虑的讨论,你怎么看?

《参考解析》

缓存设计与 Copy-On-Write:讲缓存要落到「key 怎么定、什么时候失效、穿透/击穿/雪崩怎么防」这一套。如果被问的是一层本地缓存,重点在数据结构与更新策略:读多写少用不可变快照替换,读时无锁、写时构造新副本再原子换引用,这就是 Copy-On-Write 的核心——把「写时加锁」换成「写时复制、读时零锁」,代价是每次写都要复制整份数据、内存放大,且读到的可能短暂是旧版本,因此适合读远多于写、能容忍短暂不一致的场景(配置、路由表、字典)。Java 里 CopyOnWriteArrayList 就是这么实现的;如果数据量大或者写频繁,正确做法是换 ConcurrentHashMap + 分段/版本号,而不是硬扛 Copy-On-Write。

线程池设计、锁与原子变量:线程池要能说清七个参数(核心数、最大数、空闲存活、时间单位、任务队列、线程工厂、拒绝策略)与执行顺序:先占核心线程,满了入队,队列满了才扩到最大线程,再满就走拒绝策略——最常见的坑就是把无界队列配上很小的最大线程数,结果最大线程数永远用不上。参数估算按任务类型分:CPU 密集取核数 + 1 上下,IO 密集按「核数 × (1 + 等待时间/计算时间)」估,并且必须自定义线程名、给有界队列、明确拒绝策略与监控。什么时候用锁、什么时候不用:只在有共享可变状态且必须保证复合操作原子性时加锁;能用无状态设计、局部变量、不可变对象、ThreadLocal 隔离的就不加锁;只在计数、标志位这类单变量场景才用原子变量,因为它只保证单个操作原子,多个原子变量组合起来并不原子,该上锁还是得上锁。

消息顺序与不丢:顺序性要拆成生产端、存储端、消费端三段。生产端指定同一个 key(订单号/用户 ID)走同一分区(Kafka)或同一队列,才能保证同 key 有序;存储端 Kafka 单分区天然有序,RabbitMQ 得靠单队列单消费者;消费端是最大的坑——即使消息有序到达,多个消费者并行处理也会乱序,要么把并发度压到 1、要么按 key 做内存队列分流(同 key 落到同一线程)。代价是顺序与吞吐天然矛盾,要按业务判断哪些 key 真的需要有序。不丢同样三段:生产端用带回调的确认/同步发送 + 重试;Broker 端做副本(acks=all、min.insync.replicas)并落盘;消费端改手动提交 offset、处理成功再提交。配合消费端幂等(唯一键去重、状态机判断),才是完整的「至少一次投递 + 幂等消费」。

索引失效与大表处理:索引失效的典型场景:对索引列做函数或表达式运算、隐式类型转换(字符串列传数字)、LIKE '%x' 前导通配、OR 连接了未建索引的列、违反最左前缀、!=/NOT IN 走全表(优化器判断选择率时可能放弃索引)、以及优化器基于统计信息判断走索引更贵。排查手段是 EXPLAIN 看 type / key / rows / filtered,必要时 ANALYZE TABLE 更新统计信息。数据量大之后的路子:先看能不能靠索引与 SQL 改写解决;再考虑冷热分离、归档、分区表;最后才是读写分离、分库分表——分表要提前想清分片键(避免跨片查询与热点)、全局 ID、以及分页/聚合的改造成本,属于最后手段。

MVCC 与手撕题:MVCC 一句话概括:InnoDB 用 undo 版本链 + ReadView 让读不加锁也能看到一致快照,RC 每次读都重建 ReadView、RR 只在第一次读时建,所以两者对「别人已提交的新数据」表现不同;快照读与当前读(FOR UPDATE / LOCK IN SHARE MODE)是两套读法,别混着答。K 个一组翻转链表属于链表操作里的中等题,稳妥写法是哨兵节点 + 分组:每轮先探测后面是否还剩 k 个节点,不够就保持原样直接返回;够则把这一组内部反转(三个指针 pre/cur/next 迭代),再把组前驱指向组内新头、组尾指向下一组。要主动说明边界:k=1、链表长度不足 k、最后一组不翻转、以及空间 O(1)、时间 O(n)。写之前先把「不足 k 个不翻转」这个口子跟面试官确认一遍,能省掉返工。

意向城市、技术栈与 AI 焦虑这类「软问题」:这类问题看似闲聊,实际上在筛稳定性与匹配度,答得含糊很容易在最后一步掉分。意向城市要给明确答复并给理由(家庭、发展、已投递的其他机会),不要「都行」;被问是否接受写 Java,最好是「接受 + 有依据」(写过什么、上手路径),而不是硬撑或直接拒绝,因为进去大概率就是写 Java。聊到 AI 焦虑,面试官想看的通常不是情绪而是行动:你怎么用 AI 工具提效、怎么保证它产出的代码质量、以及你判断哪些能力不会被替代(把问题定义清楚、做取舍、为结果负责)。整场复盘下来,项目题被追问到「设计取舍」层答不上来,往往比八股答错更致命——准备时至少给每个项目准备一个「我做了什么决策、对比了什么方案、结果怎么验证」的完整故事。