尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

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

字节跳动算法岗校招复盘:从KMP到动态规划的备考实战指南

字节跳动算法岗校招复盘:从KMP到动态规划的备考实战指南 2018年那波校招字节跳动的算法岗算是当时最抢手的坑位之一。我到现在都记得笔试系统里那几道题跳出来的时候心里那种“既兴奋又发怵”的感觉。兴奋是因为字节的算法题一直公认“质量高、不套路”发怵是因为它真的太能考出你的基本功了。最近不少学弟学妹问我当年到底考了什么、怎么准备我索性把那次校招的完整复盘写出来从笔试到面试、从题型到现场手撕代码的技巧一次性讲透。就算你现在不是投字节这份备考思路和避坑经验也完全能嫁接到其他大厂的算法岗面试里。这篇文章会按照当年的真实流程来拆解先讲整个校招的筛选逻辑和考察重点再逐题拆笔试环节的高频题型然后是三轮面试的实战记录最后是现场写代码的正确姿势和我总结的备考路线。不管是准备找工作的应届生还是想转行做算法的朋友都能从里面找到可以直接抄作业的东西。1. 2018年字节跳动算法岗校招全景考察逻辑与筛选节奏1.1 招聘流程全貌从网申到Offer的时间线2018年字节跳动校招算法方向第一批的整体节奏非常紧凑从网申到offer大概走了一个月到四十天。流程基本是线上网申 → 在线笔试 → 技术面试三轮 → HR面 → Offer审批。算法岗的在线笔试一般是4道编程题限时90到120分钟题目分布覆盖字符串、数据结构、图论、动态规划和简单数学题。当年通过笔试的标准不算特别高但也不低稳妥一点需要ACAccept通过全部测试用例两道半以上如果只AC一道题基本就没有面试机会了。这里有个关键点很多人会忽略字节的笔试是分层筛选的。你所在批次、投递的岗位方向、简历评级都会影响同一套题里你的“隐形通过线”。所以千万不要抱着“题目太难大家都不会”的心态策略上必须全力保前两题AC第三题尽量拿部分分第四题能写多少写多少。我当年就是前两题AC得很快第三题写了一个朴素解法拿了部分分第四题输出样例过了几个case整体算下来稳进面试。1.2 考察核心算法工程师需要具备的五大能力复盘下来字节算法岗面试官想考察的能力模型其实很清晰我把它们归纳为五个维度算法与数据结构基本功数组、链表、栈、队列、二叉树、图、哈希表以及排序、二分、双指针、滑动窗口、DFS/BFS等经典算法。这是笔试和一面的大头占比超过50%。数学基础概率论贝叶斯、期望、分布、线性代数矩阵运算、特征值和最优化方法梯度下降、凸优化。这些会出现在机器学习推导题和某些算法题里。机器学习和深度学习理论LR逻辑回归、SVM支持向量机、决策树、GBDT梯度提升树、XGBoost、CNN卷积神经网络、RNN/LSTM循环神经网络、Attention机制等。二面会深挖到这里。工程与系统设计思维推荐系统的整体架构、特征工程、AB实验设计、大数据处理。这部分在三面出现。沟通与临场反应能力面试官给出一道题后你能不能快速理清思路、主动沟通边界条件、讲清楚复杂度最后干净利落地写出代码。这个模型不仅是字节在用2018年前后各大厂算法岗的筛选逻辑基本都往这个方向靠。说白了算法工程师不光要会“算”还要能“讲清楚怎么算”更要能“在压力下算对”。1.3 为什么2018年的真题放到现在依然值得刷可能有人会问都过去这么久了翻2018年的真题还有意义吗我觉得意义非常大甚至比刷当年的LeetCode热门题更有价值。原因很简单算法岗校招的考题是有“代际遗传”的。像KMPKnuth-Morris-Pratt字符串匹配算法、TopK、最大子序和、LR推导这类题目到今天依然是各厂面试题库里的常客。字节的题库虽然一直在扩充但核心考点从未变过变的只是包装方式和题目背景。另外2018年是算法岗校招竞争激烈程度的一个分水岭。那一年字节的面试风格基本定调了手撕代码必须现场跑通、讲不清复杂度的解法会被追问到底、项目经历会被深挖到推公式层面。这套标准后来被很多公司参考所以吃透这批题目等于提前适应了整个行业对算法工程师的要求。2. 笔试环节深度拆解4道题定去留2.1 字符串与模式匹配KMP算法是送分题还是送命题字节笔试和面试里字符串题出现的频率极高尤其是模式匹配类。KMP算法是其中的天花板级考点也是很多人的恐惧来源。网上关于KMP的教程非常多但大多数讲得太绕我用自己的话把这个算法剥开揉碎讲一遍。KMP解决的核心问题是在一个主串S里查找模式串P出现的位置如果暴力匹配时间复杂度的最坏情况是O(n*m)而KMP能把复杂度优化到O(nm)。它的核心思想是当匹配失败时利用已经匹配成功的前缀信息让模式串“跳着走”而不是老老实实回退一格。这里的关键数据结构是next数组。以模式串pabacaba为例next[i]的定义是p[0...i]这个子串中最长的相等前缀和后缀的长度。我直接手写一遍计算过程i0子串是a没有真前后缀next[0]0i1子串是ab前缀集{a}后缀集{b}没有相等next[1]0i2子串是aba前缀{a,ab}后缀{a,ba}最长相等是a长度1next[2]1i3子串是abac前缀{a,ab,aba}后缀{c,ac,bac}没有相等next[3]0i4子串是abaca前缀{a,ab,aba,abac}后缀{a,ca,aca,baca}最长相等是a长度1next[4]1i5子串是abacab前缀{a,ab,aba,abac,abaca}后缀{b,ab,cab,acab,bacab}最长相等是ab长度2next[5]2i6子串是abacaba前缀{a,ab,aba,abac,abaca,abacab}后缀{a,ba,aba,caba,acaba,bacaba}最长相等是aba长度3next[6]3所以next数组是[0, 0, 1, 0, 1, 2, 3]。代码实现如下def get_next(p): n len(p) nxt [0] * n j 0 for i in range(1, n): while j 0 and p[i] ! p[j]: j nxt[j - 1] if p[i] p[j]: j 1 nxt[i] j return nxt这段代码里最核心也最容易写错的是while循环那一句当字符不匹配时j要回退到nxt[j-1]对应的位置而不是直接清零。理解这个回退过程的关键是意识到next数组本身就是“失败时模式串应该跳到哪”的决策表。我当时笔试遇到字符串题第一反应都是先检查能不能用KMP如果题目给的字符串规模在10^5级别那几乎可以肯定出题人期望的解法就是KMP或者类似思想的线性算法。提示KMP不是唯一选择。如果面试官只要求“不超时”遇到变种题时可以用更工程化的解法比如Z算法、字符串哈希甚至直接调库。但在笔试里自己能写出来的算法才是好算法所以KMP的模板必须背到肌肉记忆的程度。2.2 排序与TopK从快排到堆排的必杀技排序算法是笔试和面试的“基础设施”几乎每套题里都会匿名出现。2018年字节第一批笔试里虽然没有直接出“手写快排”这种题但排序思想被嵌套在TopK、区间合并、贪心调度这些问题里。TopK问题我当时用的是小顶堆最小堆解法这几乎是标准答案。思路是这样的维护一个大小为K的小顶堆遍历所有元素如果当前元素比堆顶大就把堆顶弹出把当前元素压进去。遍历结束后堆里的K个元素就是最大的K个。时间复杂度O(n log K)空间复杂度O(K)。如果K远小于n这个复杂度比直接排序后取前K个的O(n log n)要优一个量级。import heapq def top_k_largest(nums, k): heap [] for num in nums: if len(heap) k: heapq.heappush(heap, num) elif num heap[0]: heapq.heapreplace(heap, num) return heap我当时笔试遇到TopK的变形题题目给了10亿个数求最大的100个。看到“10亿”这个量级就应该条件反射一定要用堆而且要跟面试官或阅卷系统确认内存限制。如果是单机内存受限的场景这题还可以扩展成“分组TopK 归并”的多路解法这也是后面系统设计题里高频出现的思路。快排的Partition思想也要烂熟于心。它不光用来排序还是求第K大数的O(n)期望复杂度解法BFPRT算法里也有它的影子。面试官很爱问“快排最坏情况是什么怎么避免”答案是当每次选取的pivot都是最大或最小元素时复杂度退化成O(n^2)。解决办法是随机选取pivot或者在递归层数过深时切到堆排序这种混合算法叫Introsort是STL sort的底层实现思路。2.3 贪心与动态规划套路躲不开的两个大块头动态规划DP和贪心算法是笔试占分最重的两块也是区分度最大的部分。字节尤其喜欢出区间调度、最大子序和、01背包这类经典模型。先讲贪心算法里最经典的区间调度问题给定N个区间求最多能选择多少个互不重叠的区间。思路是先按结束时间排序然后依次选择“结束最早且不与前面已选区间冲突”的区间。为什么按结束时间排序而不是开始时间因为结束时间早意味着给后面的区间留下的空间更大这是贪心选择性质的直观解释。这个证明在面试中一定要能复述出来通过交换论证法证明任何最优解都可以被替换成贪心解而不破坏最优性。再讲动态规划最大子序和是入门必做的一道题但面试官会不断加深。先给基础版def max_subarray(nums): cur nums[0] # 以当前元素结尾的最大子数组和 ans nums[0] for i in range(1, len(nums)): cur max(nums[i], cur nums[i]) ans max(ans, cur) return ans核心转移方程是dp[i] max(nums[i], dp[i-1] nums[i])意思是以第i个元素结尾的最大子数组和要么是它自己“从新开始”要么是把它接到前面最优子数组后面。为什么这样是对的因为子数组必须是连续的所以dp[i]只跟dp[i-1]有关不需要考虑更早的状态。这个“只跟前一个状态相关”的性质也是把O(n^2)暴力法优化到O(n)的关键。2018年字节笔试里的DP题没有直接考裸模型而是套了一个“矩阵从左上到右下路径最大权值和只能向右向下走”的壳子。这题的本质是把二维DP直接套在网格上转移方程是dp[i][j] grid[i][j] max(dp[i-1][j], dp[i][j-1])。我建议备考时把这类“披着场景外衣”的DP题专门整理一个本子它们的套路都是定义状态 → 写转移方程 → 确定边界条件三步走完切忌一上来就跳进题目的故事里。2.4 机器学习基础笔试里的LR、SVM与GBDT笔试除了纯算法题还有不少机器学习基础题。2018年字节的算法岗笔试题里就有选择题和简答题涉及逻辑回归、支持向量机、决策树和集成学习。这里我挑三个最容易出题的点展开。逻辑回归LR最常考的推导是为什么损失函数用交叉熵而不用均方误差答案是如果LR用MSE做损失函数梯度表达式中会出现sigmoid函数的导数项而sigmoid的导数在两端趋近于0会导致梯度消失收敛极慢。交叉熵结合sigmoid后梯度形式是(p - y)x干净利落不存在导数饱和问题。SVM的核心考点是硬间隔最大化的优化问题。这里要能写出原始问题、拉格朗日函数、对偶问题并且解释为什么需要对偶。核心原因是对偶问题中样本只以内积形式出现这使得核技巧Kernel Trick可以直接替换内积函数从而把线性不可分的数据映射到高维空间后实现线性分隔。面试官如果把这个问题追问到底还会问KKT条件——这直接关系到哪些样本会成为支持向量。GBDT梯度提升树和XGBoost的区别也是一个高频题。我的回答框架是GBDT是加法模型、前向分步算法、CART回归树做基学习器XGBoost在GBDT基础上加了二阶泰勒展开、正则项、列采样、缺失值自动学习方向并且支持并行所以效果和速度通常更优。记住一个关键点XGBoost里基学习器除了做回归还能在分裂时用“增益最大的特征”来做分叉增益公式里面带正则项惩罚这是它控制过拟合的重要手段。我当年笔试的机器学习部分得分策略是选择题要做到90%以上正确率简答题能画图就画图、能用公式就写公式。阅卷人看简答题的速度非常快一个工整的公式推导和一张清晰的模型示意图远比大段文字描述更有说服力。3. 面试环节实战三轮技术面HR面全记录3.1 一面手撕数据结构是标配字节的一面非常规范节奏控制在45分钟到1小时。开场一般是两分钟自我介绍然后就直接进入手撕代码环节。我那次一面一共写了三道题。第一道是反转链表。这题看似简单但面试官会不断加码先反转整个链表然后反转链表的前N个节点最后变成“反转链表区间[m, n]的节点”。每一层递进都在考察你对指针或引用的控制能力。写反转链表时有个细节必须注意pre指针初始化为None循环里要先把next指针保存下来再改指向这是最多人翻车的地方。Python版本如下def reverse_list(head): prev None curr head while curr: next_node curr.next curr.next prev prev curr curr next_node return prev第二道是二叉树的层序遍历。普通层序用队列实现面试官随后追问怎么区分每一层解法是在while循环里记录当前层的队列大小然后一次性处理完这一层。这个技巧也叫“层内循环”是很多二叉树变种题的基础。第三道是编辑距离Levenshtein Distance。这算是一道中等偏上的DP题。状态定义是dp[i][j]表示word1的前i个字符变成word2的前j个字符需要的最少操作数。转移方程分三种操作删除dp[i-1][j]1、插入dp[i][j-1]1、替换相同则不变不同则dp[i-1][j-1]1。写完之后面试官一定要求你跑一遍示例数据这时候千万别图省事跳过逐格填表是展示你真正理解DP的最好机会。一面结束时面试官会问“你有没有什么问题想问”这里一定要提前准备两个高质量问题比如“团队目前主要用哪些模型做推荐排序”“对新人校招生有什么培养计划”。不要问“我这轮过了吗”这类尴尬问题更不要说你没问题。3.2 二面从项目深挖到机器学习理论二面通常就是技术深度面面试官一般是团队的资深工程师或技术Leader。开场会先花10到15分钟聊简历里的项目然后顺着项目里的算法点深挖最后再来一两道算法题。我当时项目里用了LSTM做序列数据建模面试官顺着问了一连串问题LSTM为什么能解决RNN的梯度消失问题它的门控机制分别起什么作用如果序列特别长LSTM还有什么不足怎么优化我当时重点讲了遗忘门和细胞状态的设计解释了为什么细胞状态上的线性加法可以保证梯度跨时间步稳定传播然后用注意力机制来弥补长序列信息丢失的问题。深挖完项目面试官出了一道“合并K个有序链表”的题目。这题最简单的解是逐个合并时间复杂度O(K^2 * N)但最优解是用优先队列堆维护K个链表的当前头部每次弹出最小节点并补入该链表的下一节点时间复杂度O(N log K)。代码要现场写完整包括heapq的用法和链表节点的定义。这里有个小坑Python里堆元素如果是自定义对象需要包装成元组(值, 索引, 节点)来避免类型比较报错。二面的机器学习理论题考了过拟合的解决方案。我的回答框架是从数据层面增加数据量、数据增强、模型层面降低模型复杂度、正则化、Dropout、训练层面早停、交叉验证、集成学习三个维度展开。说完之后面试官追加了一个问题“如果加了L1正则和L2正则解出来的参数分布有什么区别”这个问题考的是稀疏性——L1能产生稀疏权重因为它在一个正方形解空间顶点处更容易与损失等高线相切而L2的解分布在圆形边界上参数整体变小但不会归零。3.3 三面推荐系统场景设计与开放题三面一般是考察系统设计能力有时候是交叉面面试官可能不是算法方向而是后端或者全栈背景。这一轮我遇到的是推荐系统场景设计题“如果让你设计今日头条的信息流推荐策略从用户刷到第一条内容开始你会怎么架构这个推荐流程”这道题没有标准答案但考察的层次很清晰召回、排序、重排三个环节必须讲清楚。召回阶段我讲了基于用户行为协同过滤UserCF/ItemCF、基于内容相似度的召回、热门兜底召回排序阶段我会选择引入LR或GBDTLR模型把用户特征、物品特征、上下文特征拼成一条宽表重排阶段要考虑多样性和新鲜度控制比如对连续相似内容的打散操作。系统设计题还有一个隐藏考点AB实验。面试官一定会问“你怎么验证新排序模型比老模型好”。这里要回答实验分组策略、样本量预估、显著性检验方法以及一个容易被忽略的问题——实验和验证之间的数据一致性比如新模型上线后会不会造成用户行为反馈分布变化导致离线评估指标失真。三面一般不考笔试那种纯算法题了但会出一些概率题或逻辑题。我被问了一道很有意思的题“有一个不均匀的硬币抛出正面的概率是p不知道p的具体值怎么用这个硬币模拟出一个均匀的伯努利试验”标准解法是掷两次硬币记录结果为(正,反)时输出1为(反,正)时输出0如果两次结果相同则重新掷。因为(正,反)和(反,正)的概率都是p(1-p)所以概率相等。这个题考的是概率思维和构造能力属于“看似简单但一紧张就容易绕进去”的题型。三面结束后通常紧接着HR面。HR面会问薪资期望、入职时间、实习经历、有没有其他offer、能不能接受加班等问题。这一轮不需要展示技术实力但要注意谈到其他offer时不要撒谎也不要太过压低自己的期望。可以坦诚地说“目前手里有几个其他offer但字节这边的业务方向更匹配我的技术栈”这反而是加分的表达。4. 现场手撕代码的正确打开方式4.1 写代码前的“定调三句话”很多人一看到题目脑子里有了思路就开始埋头敲键盘这在面试里是大忌。面试官想看的不是你的打字速度而是你的思考过程。我总结的经验是动笔之前先跟面试官说三句话。第一句话复述题目用自己的话把题目重新说一遍确认自己理解无误同时让面试官知道你读懂了。第二句话讲思路告诉面试官“我打算用哈希表来存储访问过的元素再遍历一遍检查目标差值是否存在时间复杂度O(n)空间复杂度O(n)”。第三句话问边界条件“数组为空怎么处理数组里元素有重复怎么办元素是整数还是浮点数”这三句话说完面试官基本就对你有了一个“思路清晰”的初步印象就算后面代码卡壳印象分也不会太低。我见过太多候选人写代码的时候全程沉默最后发现思路完全跑偏。反过来如果你边说边写面试官发现你理解有偏差时还能及时提醒你这反而是给你“捡分”的机会。手撕代码从来不是一个人闷头做题而是一场和面试官的协作。4.2 边界条件与代码规范一次bug-free的秘诀面试写代码绝对不能只追求“能跑”还要追求“一次写得对”。要达到这个水平核心是在编码过程中刻意思考边界条件。常见的坑我列出来给大家参考空输入链表头是None、数组长度为0、字符串为空这类情况必须前置判断。单元素输入很多递归和迭代算法在只有单个元素时会走特殊的逻辑分支。数值溢出涉及加法乘法时考虑是否用longPython没有溢出问题但C/Java必须考虑。数组越界动态规划里dp数组通常开n1大小就是为了处理“前0个元素”作为边界这也是dp题最经典的一个细节。浮点数比较不要直接用等于判断浮点数给定一个epsilon容差来判断。还有一个被忽略的细节变量的命名规范。面试官会盯着你的代码看用a、b、c这种无意义命名会非常减分。我习惯用l, r表示左右指针pre, cur表示链表操作的前继和当前节点dp表就老实叫dp加上注释说明dp[i][j]的含义。代码整洁度和正确性是并列的重要项一条写出又臭又长还不能自解释的代码就算答案对了面试官也会怀疑你的工程素养。4.3 复杂度分析把“最优”讲清楚写完代码之后面试官一定会问“时间复杂度是多少空间复杂度是多少还能不能优化”这里的目标不是背出答案而是把你的分析过程展示给他看。拿前面合并K个有序链表来举例。使用堆做合并每个节点进出堆一次堆的大小最多为K每次堆操作是O(log K)总时间复杂度是O(N log K)其中N是所有链表的节点总数。空间复杂度是O(K)因为堆里最多存K个元素。如果你用两两合并第一轮合并两个链表需要O(2N)的时间第二轮O(3N)一直到第K轮O(KN)总复杂度是O(K^2 * N)。讲清楚这两个方案的复杂度差异本身就是展示你算法功底的关卡。优化层面你还可以提一嘴“如果K非常大但链表长度很短可以考虑分治合并复杂度会从O(NK)降到O(N log K)如果内存装不下全部链表头还可以用败者树或外排序的思路”。能主动说出这些扩展点会让面试官觉得你对问题的理解是结构化的而不是背了一两道题的解法。5. 常见考点速查表与备考建议5.1 高频考点速查表我把2018年字节算法岗校招以及后续几年仍然高频出现的考点整理成一张速查表方便你对照自查考点类别典型题型推荐解法优先级字符串KMP匹配、最小覆盖子串、最长回文子串KMP、滑动窗口、Manacher高排序与选择数组第K大、TopK、区间合并快排Partition、小顶堆高链表反转、环形检测、合并K个有序链表三指针、快慢指针、堆高二叉树层序、遍历迭代版、最近公共祖先队列、栈、递归高动态规划最大子序和、编辑距离、背包、路径问题一维/二维DP、滚动数组高贪心区间调度、跳跃游戏排序贪心选择中高图论拓扑排序、最短路径、连通分量Kahn算法、Dijkstra、Union-Find中机器学习LR推导、SVM对偶、GBDT/XGBoost、聚类公式推导面试口语表达高深度学习CNN各层、反向传播、LSTM门控、Attention概念推导结合中高智能优化粒子群、模拟退火、遗传算法了解原理与适用场景低数学与概率不均匀硬币模拟、期望计算、排列组合构造法、贝叶斯公式中系统设计推荐系统、AB实验、特征工程召回-排序-重排框架中高看到表格最后两行你可能会有疑问粒子群、模拟退火这类算法真的会考吗2018年那会儿出现在算法岗笔试的概率不高但如果投的是AI Lab或者偏研究的方向面试官有可能聊到。我的建议是花两小时了解核心思想即可不要在这些低频考点上花太多时间把精力留给KMP、DP、LR推导这类必考内容。5.2 三个月高效备考路线从LeetCode到模拟面试备考时间线我建议拉满三个月分成三个阶段第一个月是“刻意练习期”。每天刷4到6道LeetCode题目按数据结构分类刷而不是随机刷。优先刷数组、链表、哈希表、二叉树和字符串这五类基础题目标是把这些类型的“手感”练出来。每道题都要做两遍第一遍不看书独立思考和实现第二遍对照优秀题解学习更优解法和代码风格。如果一道题想了20分钟还没有完整思路直接看题解不要死磕。第二个月是“算法专题期”。进入动态规划、贪心、图论、二分搜索、滑动窗口这几个高频专题。动态规划建议单独用一周时间集中刷题从背包、最长公共子序列、编辑距离、最长递增子序列这些经典题入手。这个阶段还要补充机器学习理论建议把LR、SVM、决策树、GBDT的推导自己动手写一遍每写完一个公式就默念一遍它的直觉解释。第三个月是“实战模拟期”。开始在牛客网上做真题套题模拟笔试环境卡时间、看通过率。每周至少做两套真实难度的笔试题做完之后把每道题按“考点-解法-复杂度-易错点”四栏整理到错题本里。同时找同学或朋友互相模拟面试一方出题、一方手撕尽量还原面试的真实紧张感。我当年就是这么练的第一次模拟面试紧张到手抖等到真正面试时反而因为熟悉了节奏而放松下来。这里额外说一句关于“投机取巧”的问题。有些人喜欢押题觉得“字节爱考DP我就只刷DP”这种策略风险极大。算法面试考察的是你在压力下解决新问题的能力如果你只熟悉一两个题型遇到变种题很容易翻车。反过来也说明了一个事实刷题数量不是目标刷题后能否总结出共性的解题框架才是拉开差距的地方。写在最后的一点体会我在实际面试和后来带实习生的过程中发现算法校招最大的陷阱不是“不会做”而是“会做却不知道怎么表达”。字节的那几轮面试给我留下最深的印象就是面试官几乎全程都在引导我“说出来”——说思路、说复杂度、说为什么这么设计。这其实是一件好事因为它意味着面试是一个可以沟通和纠偏的过程。如果你能把思考过程说得清清楚楚就已经赢了一半的候选人。最后再分享一个小技巧正式面试前打开手机录音自己对着题目讲一遍完整的手撕代码过程然后回放听一听。你会惊讶地发现自己有多少次会说出“这里就是这样写的嘛”这种含糊不清的话也会发现自己的逻辑漏洞。练上几次之后表达会变得干净利落写代码的速度和质量都会有肉眼可见的提升。希望这份复盘能帮你在算法路上少踩几个坑offer尽早到手。
返回列表