ARTICLE DETAIL

资讯详情

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

字节跳动后端校招二批复盘:从算法到系统设计的高频考点

字节跳动后端校招二批复盘:从算法到系统设计的高频考点 2018年字节跳动的后端校招分了好几个批次滚动进行但“字节跳动2018校招后端方向第二批”这批题是当时讨论最多、也最值得事后复盘的一组。它不像第一批那样大量考察模板化算法题而是把基础知识点层层包装几乎每道题都在逼着你当场展示思考过程。很多人背熟了面经去面试结果在“为什么”这个环节被卡住——不是不会答而是从来没想过答案背后的原理。这篇复盘把当年这批题的考察链路拆开来讲笔试四道大题的命题逻辑、一面到终面的高频追问层级以及我后来总结的复习优先级。正在准备后端校招的同学可以直接拿它当复习框架已经工作几年的开发者也能回头自查看看自己当年是不是也在同样的坑里栽过跟头。不管你投递时用的是Java、C还是Go下面这些考察点基本都会遇到区别只是追问的深度不同。1. 这批题筛选的本质不是考你会不会是考你卡住之后怎么走1.1 为什么2018年的二批题目值得单独拿出来复盘2018年的校招环境和今天很不一样。当时流传出来的一手面经很少候选人在牛客网上拼凑信息很多题目的完整描述都是靠大家回忆出来的。第二批的题传到网上之后当时很多人的第一反应是“这道题我刷过类似的”结果自己上手一写发现处处是坑。印象最深的是一道字符串题按单词反转字符串但保持标点符号的原始位置。乍一看就是在「反转字符串中的单词」基础上加了个条件实际上多了一个状态维护的难点——你必须在遍历过程中记录某个字符到底是单词的一部分还是标点才能决定翻转边界。不少人习惯性地先split写完才发现逗号和句号全乱套了。面试官不是不知道split能实现大部分功能他真正想看的是当条件发生变化时你能不能回到问题本身重新设计解法而不是机械地套模板。1.2 二批与一批的核心差异基础题被加了“场景化包装”把一批和二批的题目放在一起对比最明显的差别是二批的题目多了一层工程场景包装。比如一批考标准的top-k问题二批就会把它包装成“某个服务每秒产生大量日志需要统计最近一分钟内出现次数最多的错误码”并且要求说明数据结构的选择、内存占用和扩展性。这层包装不是没事找事。当年字节后端业务扩张非常快招进来的人很可能下个月就要处理线上高并发问题所以面试官需要筛出“能直接参与生产系统开发”的候选人。一个只会写算法模板、从不考虑内存和并发的候选人在二批的考察标准下会很吃亏。2. 笔试四道大题边界条件比核心算法更致命2.1 字符串处理题丢分重灾区字符串处理题在二批笔试里几乎是必考的而且考法和LeetCode有明显差异。LeetCode上的题目通常把输入约束写得很清楚候选人只需要在约束范围内实现但这批笔试题常常不直接给数据范围只在描述里暗示“输入可能包含空串、连续分隔符、大小写混合字符”。我印象最深的一题是移除字符串中连续重复超过两次的字符。很多人第一版代码能过示例但边界测试一跑就挂。最容易漏的有两个地方连续相同的字符刚好出现两次时要不要保留以及移除之后相邻字符如果再次形成重复需不需要继续处理——这实际上是一个类似消消乐的递归删除问题。如果不提前把状态转移想清楚用while循环也能实现但很容易漏掉“删除之后指针需要回退”这一步。我当时的做法是先不开编译器在草稿纸上把各种边界情况列出来再开始写。笔试时间确实紧但花三分钟把边界列清楚后面调试省下的时间远不止三分钟。2.2 二叉树层序变体状态机比递归更省心二叉树相关的题目这批笔试偏爱层序变体。不是普通的层序遍历输出而是在遍历过程中附带状态判断。典型的例子是判断一棵二叉树是否是完全二叉树。标准做法是BFS加一个“是否已经遇到空节点”的标记但很多人写着写着就把“满二叉树”和“完全二叉树”搞混了。完全二叉树允许最后一层不满而且最后一层的节点必须靠左排列。用BFS做时关键在于遇到第一个空节点之后后面就不能再出现非空节点。这个判断只需要一个boolean变量就能维护本质是一个极简的状态机。我看到过有候选人把这题做复杂了用递归统计左右子树高度差来判断代码长还容易在边界处出错。笔试阶段建议大家优先掌握BFS加状态标记这套解法它天然贴合层序遍历的直觉出错的概率更低。2.3 动态规划题不要急着写转移方程动态规划在二批笔试里不是最难的但一定是最能拉开区分度的。当时出现率很高的一道题是“编辑距离”的变体不过不是让算最小编辑次数而是要求输出具体的编辑方案。这类题暴露了很多人的通病状态转移方程背得很熟但dp数组初始化的含义没想清楚。比如编辑距离中第一行和第一列分别代表“空串变成另一个串的代价”很多人写转移方程的时候把i和j的物理意义搞混了导致后面回溯编辑方案时完全对不上号。我的建议是拿到dp题先花一分钟把dp[i][j]的定义用自己的话说一遍再想初始化最后才写转移方程。定义错了后面全错而且现场很难发现。这个习惯在LeetCode上不一定能看出来因为判题只关心最终结果但在笔试和面试场景中面试官会看你草稿纸上的推导过程定义是否清晰一眼就能判断。2.4 位运算和数据范围题目里的隐藏提示二批笔试的压轴题经常带着“数据范围陷阱”。举个例子给你一个数组其他数字都出现三次只有一个数字出现一次让你找出来。用HashMap肯定能过功能测试但如果你注意到题目底下还写着“尽量不使用额外空间”就会明白面试官想考察的是位运算思路。HashMap直观但需要O(n)的额外空间位运算解法用几个int变量累计每一位上1出现的次数再对3取模空间复杂度降到O(1)。同样的题目放在LeetCode上两种解法都能过但在笔试场景里出题人考察的就是你在资源受限时能不能想到更底层的方案。笔试现场不要一上来就选最舒适的数据结构先看一眼数据范围和内存限制再决定方案。3. 一面高频考点从答案到原理追问到你不会为止3.1 TCP三次握手不是背过程是推演异常场景一面必考TCP这一批的考察深度明显比一批深。如果只是机械地背“客户端发SYN服务端回SYNACK客户端再回ACK”面试官用两三个追问就能把你打回原形。常见的追问链路是这样的三次握手中为什么需要第三次如果第三次ACK丢失了会发生什么服务端此时处于什么状态如果客户端长时间不发数据连接会不会被断开最后一个问题会牵扯出TCP的KeepAlive机制以及操作系统里tcp_keepalive_time这个参数。再往下面试官可能还会问如果不收第三次ACK服务端会维护多少半连接这就是SYN Flood攻击的基本原理。到这里你会发现背的那点东西只够撑过前两个问题。我当时应付这种追问的方法很简单把每个协议的“为什么”和“如果…会怎样”都提前推演一遍。TCP三次握手不是背三行交互而是理解服务端的状态机——CLOSED、LISTEN、SYN_RCVD、ESTABLISHED每一步的状态转移都是有原因的。3.2 操作系统进程线程、零拷贝、IO多路复用操作系统在一面里常考的考点有三个进程与线程的区别、零拷贝、IO多路复用。进程与线程的区别如果只答“进程是资源分配的基本单位线程是CPU调度的基本单位”面试官马上会接着问为什么线程切换比进程切换轻量同一个进程下的两个线程共享哪些资源不共享哪些如果两个线程同时访问同一个全局变量加锁的本质是在保护什么这最后一问其实已经跳到内存模型和缓存一致性了很多人在这一层就答不上来。零拷贝这个考点当年问的频率很高因为字节后端大量使用Netty而Netty底层依赖零拷贝。面试官不会直接问“零拷贝是什么”而是会问“从磁盘读文件并通过socket发送出去数据在用户态绕了一圈到底做了什么”。如果能把readwrite、mmapwrite、sendfile三条路径的上下文切换次数说清楚这道题基本就拿下了。IO多路复用考察的核心是select、poll、epoll的区别。建议准备一个具体场景来分析如果服务端有一万个连接但同一时刻只有几十个连接有数据到达select会怎么遍历epoll又是怎么通过事件回调避免无效遍历的最好还能提一下epoll的LT和ET两种触发模式面试官经常在这里追问。3.3 Java并发从synchronized到AQS的完整应答链如果你的投递方向是Java后端并发编程一定是一面的重头戏。这批面试的考察风格是“从一个关键字出发不断向下钻”。从synchronized开始先问它锁的是什么是实例还是类然后问偏向锁、轻量级锁、重量级锁的升级过程以及各自升级的条件再往深了问synchronized和ReentrantLock的区别。如果你答出“ReentrantLock基于AQS实现”那下一个问题几乎必然是AQS的CLH队列是怎么工作的head和tail节点分别代表什么。对比维度synchronizedReentrantLock实现层面JVM指令级JDK的AbstractQueuedSynchronizer锁获取方式自动获取/释放手动lock/unlock需finally释放是否可中断不可中断lockInterruptibly可响应中断是否公平非公平可指定公平或非公平条件队列只有一个等待集支持多个Condition可精确唤醒这条链路看似很长但问题之间是有逻辑的synchronized是JVM层面的锁ReentrantLock是JDK层面的锁AQS是ReentrantLock的底层骨架CLH是AQS的等待队列模型。准备的时候把这条链捋顺不要一个知识点一个知识点孤立地记面试时才能顺着面试官的节奏一层层往下答。4. 二面实战题从MySQL索引到缓存架构现场推演比背书重要4.1 MySQL索引最左前缀为什么不是“从左边开始”二面聊到数据库上来第一题大概率是索引。这一批的高频问题包括B树相比B树的优势是什么为什么MySQL的InnoDB选择B树作为索引结构最左前缀原则的底层原因是什么最左前缀这个问题很多人的回答是“查询条件必须从索引的最左列开始”这个答案没错但很表面。面试官想听的是联合索引(a, b, c)底层是一棵先按a排序、a相同再按b排序、b相同再按c排序的B树。所以如果你跳过a直接查b就无法利用索引的有序性但如果你查a1 AND c1其实可以走到索引只是b这个维度起不到筛选作用。如果能答到这里面试官多半会追加一个实战问题“订单表查询最频繁的条件是userId和createTime你会怎么建索引”这里的坑在于如果分别建两个单列索引MySQL通常情况下只能用到其中一个而联合索引(userId, createTime)能同时满足用户维度的过滤和按时间排序的需求。回答时最好主动补一句“覆盖索引”如果查询字段都包含在索引里就能避免回表。这个主动补充在面试里很加分。4.2 缓存穿透、击穿、雪崩背结论不如说清取舍缓存话题在二面的出现率极高。面试官问到缓存时几乎不会满足于“穿透用布隆过滤器、击穿用互斥锁、雪崩用随机过期时间”这种标准答案。穿透问题的本质是“查了一个数据库里不存在的数据导致每次请求都打到数据库”。布隆过滤器能解决一部分问题但要主动说清楚它的误判率从何而来、hash函数数量怎么选、以及布隆过滤器不支持删除这个限制。如果不想用布隆过滤器缓存空值也是方案但要注意为缓存空值设置较短的过期时间否则会带来和数据库的一致性问题。击穿问题的关键不是加锁动作本身而是锁的粒度。对单个热点key加互斥锁会导致大量请求串行化所以通常会配合“锁加双重检查”先检查缓存有没有没有再尝试加锁拿到锁之后再看一次缓存还没有才查数据库。这个双重检查和单例模式里的双重检查锁是同一个思路面试官其实在考察你能不能把并发编程的知识迁移到缓存场景中。缓存问题根因常见应对容易被追问的点穿透查询不存在的数据布隆过滤器、缓存空值误判率、删除限制、空值过期时间击穿热点key突然失效互斥锁、逻辑过期锁粒度、双重检查雪崩大量key同时失效随机过期时间、多级缓存过期时间的随机范围怎么定4.3 现场设计短URL服务从单机到分片的推导过程系统设计题在二批里出现频率不低要求没有社会招聘那么高核心是看候选人有没有工程思维。印象中出现率很高的一道题是“设计一个短URL服务”。这类题有套路可循先明确限制条件每天生成多少短链接、有效期多长、需不需要统计点击来源再设计存储结构最后讨论如何生成短码。存储上最简单的方案是自增id加base62编码生成短码优势是天然不冲突劣势是id规律太明显别人可以通过递增id遍历你的所有短链。面试官问到这一步你要能接得上“用带混淆的自增id”或“用雪花算法生成唯一id”的思路。查短码映射到长码时直接查数据库但高频访问的短链要加一层Redis缓存。如果单机扛不住怎么分片按短码hash分片是一个自然的选择但要考虑“同一个短码的访问热点集中在一个分片”会不会造成热点问题。这时候可以进一步分散读取压力或者对热点短码做多级缓存。这道题没有标准答案面试官看的是你能不能一步步把约束条件摊开再逐步收敛到可行方案。准备时可以多练习这种“先澄清需求、再设计数据模型、最后讨论扩展性”的答题框架。5. 终面没有标准答案项目深挖与架构感的真实含义5.1 为什么面试官总爱问“你项目的瓶颈在哪”到了终面算法题会大幅减少项目经验成为重点。这一批终面的一个显著特点是面试官不会让你从头到尾复述项目而是直接挑一个点问瓶颈。比如你做过一个订单导出功能面试官可能问如果单次导出量从一万条变成一百万条你现在的实现会出现什么问题如果导出过程中用户重复点击你怎么防止重复生成文件如果导出任务堆积多个任务并发生成大量文件把磁盘写满了怎么办这一串问题背后考察的是你有没有用系统的视角看问题。单纯回答“把导出改成异步任务”是不够的面试官希望听到异步任务放队列里要有持久化无论是数据库表还是消息队列任务状态要能追踪也就是待执行、执行中、成功、失败这些状态要落库失败要有重试机制还要设置重试上限。这些点不需要你真的做过千万级系统但需要在设计时体现出来。我当时准备项目这轮时把简历上每个模块都按“功能、当前实现、数据量放大十倍会怎样、怎么优化”四步过了一遍这个框架在终面非常管用。5.2 限流与消息队列没做过也能答出架构感终面有时会出一个宏观一点的题目比如“你们的接口被外部调用方频繁请求怎么保护后端服务”。这题的落点是限流但面试官想听的具体方案有很多层级。单机限流最简单用令牌桶或漏桶算法很多语言的限流库都能直接实现如果需要精确限制“每秒最多1000次请求”要注意分布式环境下单机限流加起来不等于全局限流分布式限流可以用Redis加Lua脚本实现脚本里完成计数和判断保证原子性再往上限流之后流量怎么办直接返回错误还是降级这就要聊到降级策略和熔断机制。我当时回答这类问题时养成了一个习惯不管会不会先把自己的思路按“单机、分布式、降级兜底”的顺序铺开。这个顺序本身就在向面试官传递一个信息——你有架构分层意识。即使某个具体方案没做过只要链条完整得分也远高于纠结在某个细节点上。6. 复盘与准备建议按这个优先级复习比盲目刷题高效6.1 三个月的准备节奏算法、基础、项目三轮推进把二批的考察范围拆开看可以分成三个优先级算法和数据结构占比最高计算机网络和操作系统次之项目经验和系统设计再次之。建议按三轮来准备。第一轮用一个月主攻算法。不要一道题一道题地刷而是按题型分类刷数组和字符串、链表、二叉树、动态规划、图论、位运算。每种题型先搞懂通用解法模板再挑代表性题目精做做到能不看题解写出完整代码并且能跑通边界测试。第二轮用三周过基础。计算机网络重点过TCP/UDP、HTTP/HTTPS操作系统重点过进程线程、内存管理、IO模型Java方向把并发编程和JVM过一遍尤其是垃圾回收算法和内存区域划分。第三轮用两周做项目复盘和系统设计练习。把自己简历上的项目按“功能、技术栈、数据量放大后的瓶颈、优化方案”重新整理一遍。系统设计方面至少把短URL、秒杀、限流、消息队列这几个经典场景各推演一遍不要求写完整代码但要把数据模型和关键链路说清楚。6.2 最容易忽视的沟通细节把思考过程讲出来面试不是笔试除了答案正确面试官更在意的是你如何思考。很多候选人面对一个问题会沉默很久想清楚了才开口这在面试中其实非常吃亏。更有效的做法是拿到问题先复述一遍确认自己理解没偏然后把思路用“我想先考虑…然后…”这样的话说出来即使思路还不完整也没关系。面试官可以通过你的思考过程进行引导沟通反而更顺畅。我在面试中遇到过好几次“我还没说完思路面试官就点头说可以让我直接写”的情况——提前把思路暴露出来面试官对你的信任度会高很多。还有一个很实用的小细节如果某个知识点真的不会不要硬编直接说“这一块我没有深入但我了解它大概的原理是…”。诚实加上你已经掌握的部分远好过假装会然后被问穿。我自己在准备过程中每次复盘都会把“当时卡在哪、为什么卡、下次怎么避免”这三句话写下来比单纯记录题目答案有效得多。回头去看面试能不能过往往不取决于你背了多少而取决于你在紧张状态下能不能依然保持清晰的思考路径。
返回列表