2018字节跳动算法笔试复盘:高频考点与工程实践避坑指南 1. 2018年这批算法笔试到底在考什么先交代一下背景2018年是算法岗校招的一个分水岭。那一年头部互联网公司的算法HC还远没有后来那么紧张但考察的深度和广度已经明显上来了。字节跳动当时还在快速扩张期头条、抖音几个产品线都在大量招人算法方向的笔试题目整体风格偏“实用硬核”不像有些厂那样纯考背诵也不像另一些厂那样几乎全是论文题。我印象里2018校招算法方向第二批次的笔试题型大致分成三类客观选择题、主观简答题、在线编程题。选择题和简答题覆盖的内容很杂包括机器学习基础、深度学习基础、数据结构与算法基础、概率统计、线性代数甚至还会混进一两道类似“粒子群算法的基本流程”“KMP算法中next数组怎么求”这种经典但容易被忽视的题目。在线编程题则更偏向数据结构和算法本身常见的有最长公共子序列、拓扑排序、字符串匹配、排序变种题以及一些需要贪心或动态规划优化的场景题。这里我要多说一句这轮笔试的整体难度在同批校招里属于中上。它的“难”不是体现在题目本身有多冷门而是体现在时间紧、题量大、边界条件多。我认识的不少同学考完之后抱怨“选择题做不完编程题第二题还没调完就到时间了”。所以如果你想拿这批offer首要任务不是背多少冷门公式而是把高频考点练到“肌肉记忆”的程度尤其是排序、字符串匹配、动态规划这三块基本是逢考必出。另外还有一点值得注意2018年字节跳动的笔试系统已经采用了实时判题模式编程题不像后来有些平台那样允许你反复提交试错判题反馈比较严格。所以平时练习时最好养成“一次写对”的习惯尽量减少调试依赖。这个习惯在校招笔试里非常管用因为真实考试环境下你的时间确实不够用来反复调试。2. 数据结构与排序笔试里的送分题和埋伏题2.1 排序算法那张表你必须烂熟于心不管哪一年的算法笔试排序算法都是雷打不动的考点。2018年这批字节跳动校招题目里排序相关的内容覆盖了选择题、简答题和编程题三个题型。考察范围并不仅仅是“快速排序的时间复杂度是多少”而是更倾向于让你“手写快排的partition过程”或者“给定一个几乎有序的数组选用什么排序算法最优”。我把当年复习时整理的一张核心对照表放在下面按笔试出现频率排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性笔试出现频率快速排序O(n log n)O(n²)O(log n)不稳定最高归并排序O(n log n)O(n log n)O(n)稳定高堆排序O(n log n)O(n log n)O(1)不稳定高冒泡排序O(n²)O(n²)O(1)稳定中插入排序O(n²)O(n²)O(1)稳定中希尔排序O(n log n) 平均依赖gapO(1)不稳定低计数排序O(nk)O(nk)O(k)稳定低桶排序O(n) 平均O(n²)O(n)稳定低这张表需要掌握到什么程度我当时给自己定的标准是每一种算法的基本思想能用两句话讲清能写出核心代码能说出它在什么场景下最优。比如插入排序在数组“几乎有序”时效率极高因为每个元素最多移动一两次就能就位又比如快速排序最怕“每次partition都选到最小或最大的元素作为基准”所以工程实现里一般会用“三数取中”而不是直接取第一个元素做基准。2.2 快排的partition为什么值得反复手写我见过太多人在面试或笔试前表示“快排我背下来了”但一到手写环节就卡在partition上。字节跳动这种注重工程实现的公司特别喜欢让你写partition因为它能同时考察你对指针移动、边界处理、数组下标的理解。这里分享一个我后来在无数面试里验证过非常稳的写法Lomuto partition方案def partition(arr, low, high): pivot arr[high] i low - 1 for j in range(low, high): if arr[j] pivot: i 1 arr[i], arr[j] arr[j], arr[i] arr[i 1], arr[high] arr[high], arr[i 1] return i 1 def quick_sort(arr, low, high): if low high: pi partition(arr, low, high) quick_sort(arr, low, pi - 1) quick_sort(arr, pi 1, high)这个写法的好处是逻辑清晰、不容易出错面试时和面试官交流也方便。但它有个小缺点对于含有大量重复元素的数组Lomuto方案的效率会退化。遇到这种情况更推荐双路快排两端的指针同时向中间移动遇到不等于基准值的才交换或者干脆在partition之前先做一次随机选择基准值。笔试做排序类编程题时随机化基准值这个优化能救你很多次。2.3 堆排序和优先队列笔试里的“常青树”2018字节跳动的算法笔试里堆相关的内容出现得很频繁。倒不一定直接让你“手写堆排序”而是更喜欢给你一个场景问你“怎么找到数据流里最大的K个数”“怎么合并K个有序链表”“怎么求中位数”这些问题的标准解法都离不开堆。我建议你至少能手写一个简单的二叉堆包括上浮sift up和下沉sift down两个操作。很多刷题网站上有现成的PriorityQueue可以用笔试时如果语言支持直接用封装好的优先队列问题不大。但面对面试官纸面提问时“请实现一个最小堆”这类问题仍然会被问到所以手写堆的基本操作还是需要的。堆排序本身有几个容易踩的细节建堆时要从最后一个非叶子节点开始下沉不是从第一个元素开始。排序时每次把堆顶和当前堆末尾交换然后堆大小减一继续下沉。很多人会把“堆大小”和“数组长度”搞混导致排序结果错误。如果求的是第K大元素用大小为K的最小堆遍历一遍数组最终堆顶就是答案如果是求第K小用最大堆。2.4 二分查找的边界条件看似简单实则容易翻车二分查找也是字节跳动笔试的常客但它往往不是单独出题而是作为某个最优解的核心步骤藏在后面。比如“求一个有序数组里某个数第一次出现的位置”“旋转数组的最小值”这种题本质上都是二分思想。二分查找最大的坑就是边界条件。我见过太多人在while (left right)还是while (left right)之间纠结其实只需要记住一个原则你的区间定义决定了一切。如果约定[left, right]是闭区间那就用left right更新时left mid 1、right mid - 1如果约定[left, right)是左闭右开那就用left right更新时left mid 1、right mid。一旦选定了区间定义就全程保持一致不要混着用。另一个容易踩的点是死循环当left 1 right时如果mid计算用的是(left right) // 2且向下取整在某些更新逻辑下会永远停留在同一个位置。稳妥的做法是用mid left (right - left) // 2来避免整数溢出虽然笔试环境下很少真溢出但这是一个好习惯并且在更新left mid时要特别小心。3. 字符串与图论笔试里最能拉开差距的部分3.1 KMP算法next数组千万别背要会推KMP是字符串匹配里的经典算法也是2018年这次校招的热搜词之一。我看到热搜词里有“对于模式串p‘abacaba’其next数组”这种具体描述说明当时考察的深度不只是“你会不会背KMP”而是“你能不能现场推导next数组”。我强烈建议你不要死记硬背next数组的求法而是理解它的本质next[i]表示模式串前i个字符组成的子串中最长相等前后缀的长度有些教材定义为“失配后下一次匹配跳转的位置”两种定义会差1用的时候要统一。记住这个本质后做题时老老实实地对每个位置手推以p abacaba为例i 0单个字符规定next[0] -1或0看实现i 1ab前缀{a}后缀{b}无相等next[1] 0i 2aba前缀{a, ab}后缀{a, ba}最长相等前后缀为{a}长度1next[2] 1i 3abac无相等next[3] 0i 4abaca无相等next[4] 0i 5abacab无相等next[5] 0i 6abacaba前缀{a, ab, aba, abac, abaca, abacab}后缀{a, ba, aba, caba, acaba, bacaba}最长相等为aba长度3next[6] 3笔试题目如果给你p abacaba无非就是让你求next数组或利用next做匹配把上面这套推导过程练熟基本不会丢分。3.2 字典树与AC自动机笔试中“加分项”的存在2018年算法校招笔试里字典树Trie出现的概率不低尤其是涉及“前缀匹配”“敏感词过滤”“单词搜索”这类场景的题目。AC自动机则相对进阶一些但如果岗位是搜索或推荐相关方向有可能会在面试环节被问到。字典树的实现核心是每个节点存储若干子节点指针Python可以用字典存储子节点class TrieNode: def __init__(self): self.children {} self.is_end False class Trie: def __init__(self): self.root TrieNode() def insert(self, word): node self.root for ch in word: if ch not in node.children: node.children[ch] TrieNode() node node.children[ch] node.is_end True def search(self, word): node self.root for ch in word: if ch not in node.children: return False node node.children[ch] return node.is_end笔试时如果遇到“给一批单词再给一个长字符串问长字符串里出现了哪些单词”最稳的思路就是建字典树然后遍历长字符串的每个起点做匹配。虽然时间复杂度不一定是理论最优但胜在思路清晰不容易写错。AC自动机在这个基础上引入fail指针把“每个起点都匹配一次”优化成“一次扫描完成多模式匹配”属于进阶解法。3.3 图论拓扑排序、并查集、二分图字节跳动2018算法笔试的编程题里图论相关的题目几乎每年都有。最常出现的是拓扑排序Kahn算法和并查集Union-Find偶尔还有二分图相关的题。为什么爱考这些因为实际业务里遇到的依赖关系解析、任务调度、内容分类等问题底层都是这些东西。Kahn算法的思想非常朴素每次从图里拿出一个入度为0的节点删除它和它的出边重复这个过程。如果最后拿出的节点数不等于总节点数说明图里有环。这个算法在手写时几乎不会出错关键是要会用邻接表存图并且维护好每个节点的入度数组。并查集则是一种极其优雅的数据结构核心操作只有两个find找根和union合并。它非常适合处理“判断两个点是否连通”“统计连通分量数量”这类问题。笔试和面试中出现时通常需要你额外加上路径压缩和按秩合并两个优化parent list(range(n)) rank [0] * n def find(x): while parent[x] ! x: parent[x] parent[parent[x]] # 路径压缩 x parent[x] return x def union(x, y): root_x find(x) root_y find(y) if root_x root_y: return if rank[root_x] rank[root_y]: parent[root_x] root_y elif rank[root_x] rank[root_y]: parent[root_y] root_x else: parent[root_y] root_x rank[root_x] 1二分图相关题目主要是判断一个图能否被二染色。用BFS从每个未访问节点出发给相邻节点染不同颜色如果发现冲突就不满足二分图性质。这道题的隐藏考点是“你的图可能是不连通的”所以只从一个起点出发遍历会漏判。2018年那批题里有个同学就栽在这个细节上他以为题目一定给的是连通图结果有个测试用例是不连通的。4. 机器学习与深度学习算法方向的差异化战场4.1 过拟合、正则化与损失函数选择题高频区字节跳动算法岗笔试和面试非常看重机器学习基础。2018年第二批的客观题里关于过拟合、正则化、损失函数、模型评估的题目数量相当多。过拟合的常规考题包括哪些方法能缓解过拟合数据增强、正则化、Dropout、早停、降低模型复杂度L1和L2正则化的区别L1产生稀疏解L2产生小权重解为什么Dropout能缓解过拟合相当于训练多个子网络的集成。损失函数方面交叉熵和均方误差是必考项。选择题常问“为什么分类问题用交叉熵而不是均方误差”标准答案是交叉熵对应的是极大似然估计且梯度形式更有利于收敛均方误差配Sigmoid时在预测值接近0或1时梯度会很小导致学习缓慢。这类问题在面试中出现的频率也非常高属于必须张口就来的知识点。我当时复习时把常用的公式手抄了一遍反复默写。这个习惯帮我扛住了不少突击问题比如“SVM的损失函数是什么”“逻辑回归的损失函数是什么”。这些东西看起来简单但在时间紧张的笔试环境里如果你没有形成肌肉记忆是很容易在选项之间犹豫的。4.2 特征工程与评估指标会算更要会解释字节跳动的算法笔试不会只考模型理论还会考特征工程和评估指标。特征工程相关的题目大多是“给定一个场景你会怎么构造特征”这类简答题或者“缺失值处理有哪些方法”这类选择题。评估指标则是精确率Precision、召回率Recall、F1、ROC、AUC这五件套。AUC是一个很容易被小看的考点。面试官喜欢问“AUC的意义是什么”“AUC和准确率的区别是什么”。我这里提供一个能绕开常见误区的回答框架AUC衡量的是模型对正样本得分高于负样本得分的概率。它不受分类阈值的影响因此特别适合评估排序能力。对于正负样本极度不均衡的数据准确率可能严重失真但AUC依然能反映模型的区分能力。ROC曲线的绘制也需要理解但笔试阶段很少要求你手绘更多是考概念和计算。如果能动手算一个小例子比如4个样本的预测得分手算一下TPR和FPR然后画出一个折线图那这对理解整个指标体系会有很大帮助。4.3 常见模型的原理对比从LR到集成学习2018年那批笔试的简答题部分经常能看到类似“请比较逻辑回归和决策树的优缺点”这种题目。复习时我建议至少准备这样一张对比表防止临场组织语言卡壳模型优点缺点适用场景逻辑回归简单、可解释、训练快线性边界特征交互需手工高维稀疏特征、风控决策树可解释、非线形、无特征缩放容易过拟合需要剪枝表格数据随机森林抗过拟合、并行训练模型大、解释性下降中等规模数据GBDT/XGBoost精度高、能处理缺失值训练串行调参有门槛搜索排序、点击率预估如果时间允许最好能把XGBoost的目标函数推导过一遍这个在面试里是一张“王牌”。它把损失函数做二阶泰勒展开加上正则项后求解最优叶子权重。能把这个流程讲清楚面试官对你的印象会明显不一样。笔试阶段虽然不一定要求推导但简答题里如果出现“XGBoost为什么比GBDT快”“XGBoost的正则化体现在哪里”你至少需要答得出。4.4 深度学习基础反向传播与常见网络结构机器学习之外深度学习基础也是必考内容。笔试选择题常考的点包括激活函数选择ReLU为什么比Sigmoid收敛快、梯度消失和梯度爆炸的原因及对策、Batch Normalization的作用、卷积感受野计算、RNN和LSTM的区别。我印象比较深的是有一道关于反向传播的手算题给了你一个很小的神经网络三个输入两个隐藏节点一个输出让你手动计算一次前向传播和反向传播的梯度。这类题目完全能通过多练几遍拿满分。我的建议是至少手推过一个两层网络的全部梯度公式特别是Sigmoid的导数形式σ(x) σ(x)(1 - σ(x))这个几乎年年考。卷积感受野计算也是高频题。公式是RF_{l1} RF_l (kernel_size - 1) * stride_accumulated很多同学会背公式但不会用其实只需要记住一个规律当前层的感受野等于上一层感受野加上当前层卷积核能扩大的范围。手推的时候从后往前算先算最后一层的1×1再逐步向前推就不容易错。5. 笔试中的常见扣分点和避坑实录5.1 审题不清比不会做更可惜2018年那批考试结束后我复盘发现真正让人丢分的大概率不是“不会做”而是“看错题”。比如编程题要求输出浮点数并保留两位小数很多同学输出整数要求如果无解输出-1很多同学忘了写这个分支要求排序按字典序而不是按长度很多同学直接按长度排了。审题这个环节平时刷题很容易忽略因为刷题平台会明确告诉你输入输出格式你照着写就行。但真正的笔试题目往往是几百字一段的场景描述输入输出格式夹杂在文字中间稍不留神就会漏掉关键约束。我自己的习惯是先看数据范围再看输入输出样例最后才看题目正文。数据范围能直接告诉你应该用O(n)还是O(n log n)的算法如果看到n ≤ 10⁵还写O(n²)的代码基本必挂。5.2 边界条件不是每次都有“保姆”帮你处理编程题容易挂掉的另一个重要原因是边界条件没处理干净。空数组、单元素数组、全相同元素、已经有序的数组、重复元素很多的情况这些都属于笔试里的“常规暗坑”。拿排序题举例如果一个题目要求“输出排序后的结果且相同元素保持原有相对顺序”那就不能用不稳定的排序算法如果要求“去除重复元素”那就要额外处理相等情况。我曾经在一次模拟笔试里用C写了快排版本结果遇到有大量重复元素的测试用例直接超时因为那个版本没做任何针对重复元素的优化。后来我把随机化基准值加上问题就解决了。再比如二分查找类题目如果数组中所有元素都小于目标值你的left最后会停在什么位置如果所有元素都大于目标值right又会怎么移动这些都需要在草稿纸上捋清楚。我有个屡试不爽的方法写完代码后不要急着提交先在脑子里跑几个极端用例包括空数组、首尾命中、完全不存在等情况跑通了再提交。这个习惯在笔试里能为你挽回不少分数。5.3 复杂度估计失误O(n²)救不了n 10⁵笔试平台判题虽然不会直接告诉你超时但如果你写的是明显超复杂度的算法大概率会收获大量TLE。我们简单算一下假设每秒能执行约10⁷到10⁸次基础操作n 10⁵时O(n²)就是10¹⁰次操作显然会超时即使n 10⁴O(n²)的10⁸次操作也处于危险边缘。所以拿到题目后应该第一时间根据数据范围倒推算法复杂度要求。我一般按这个节奏估算数据范围可接受复杂度n ≤ 10O(n!) 或 O(2ⁿ)n ≤ 20O(2ⁿ × n)n ≤ 500O(n³)n ≤ 5×10³O(n²)n ≤ 10⁵O(n log n) 或 O(n)n ≤ 10⁷O(n)如果题目给的是n ≤ 10⁵最稳妥的思路是往二分、双指针、堆、单调栈、前缀和、哈希表这些方向想。不要一上来就写暴力解法哪怕暴力法能过样例也不代表它能过全部测试数据。5.4 手写代码时的“脏”习惯还有一类丢分属于“非技术性”的命名混乱导致自己看错变量、缩进不对导致逻辑嵌套错误、用了全局变量导致多组测试用例之间相互干扰。这些问题在平时刷题时影响不大但在真实的笔试系统里很致命尤其是多测试用例输入的情况。如果你的代码里定义了全局变量每次处理新测试用例时没有重置上一轮的结果就会污染下一轮。解决方案是把核心逻辑封装成函数每个测试用例调用一次局部变量用完即释放。不要嫌麻烦这个习惯在笔试里能省掉大量“不知道为什么错”的调试时间。另外输入输出最好用更高效的方式。Python的input()在数据量大的时候会拖慢程序建议使用sys.stdin.read()一次性读取再解析。这个优化在n较大时效果明显。5.5 时间分配“先做会做的”不是废话我见过不少同学在笔试时死磕一道编程题磕了四十分钟没写出来结果后面的选择题和简答题完全没时间做。字节跳动这种公司的笔试编程题一般有两到三道选择题和简答题的分数加起来也不算少。所以策略一定要灵活。我的建议是拿到试卷后先用两分钟把全部题目扫一遍标注出“会做”“有点思路”“完全不会”三档然后优先做“会做”的确保稳拿分再做“有点思路”的最后才是“完全不会”的。对于一些感觉能做出来但需要大量调试的编程题如果时间过半还没有进展果断止损回头检查选择题和简答题保住基础分。这个策略在2018年那批笔试里帮我拿下了不少分数。我有个朋友当时在第二道编程题上卡了四十分钟最后第三道题连题目都没看完白白丢了一道可能拿部分分的题。考完他跟我复盘说早知道就先跳过做第三题了。笔试不是“一题定胜负”而是“总分定生死”学会取舍非常关键。6. 从真题到应试算法方向的备考路线6.1 以“真题”为锚建立自己的重点清单虽然每年校招的题目不会完全重复但重点方向总体来说非常稳定。我根据2018年这次的考察方向以及后来几年观察到的变化整理了一个针对算法方向校招笔试的高频考点清单数据结构数组、链表、栈、队列、哈希表、树、堆、并查集、字典树算法思想排序、二分、双指针、滑动窗口、贪心、分治、回溯、动态规划、DFS/BFS字符串KMP、字典树、马拉车Manacher进阶图论拓扑排序、最短路径、最小生成树、二分图判定机器学习过拟合、正则化、损失函数、评估指标、常见模型对比、特征工程深度学习反向传播、梯度问题、Batch Norm、卷积/循环网络基础对照这个清单你可以快速判断自己的薄弱环节。不要均匀用力把时间花在“高频考点自己的弱项”上性价比最高。6.2 刷题计划的“两个阶段”我当时用的刷题节奏可以拆成两个阶段分享出来供参考。第一阶段是“按专题刷”。花三到四周时间把高频数据结构和算法思想各练十几道题。比如这一周专门刷二分和双指针下一周专门刷动态规划。这个阶段的目的不是刷量而是建立“看到题目就能联想到对应解法”的条件反射。每刷完一类题自己写一个总结文件记录这类题的通用思路、常见变体和易错点。第二阶段是“混着刷计时模拟”。每天挑一道新题和一道旧题模拟真实笔试的节奏来练。周末可以找一套往年的校招笔试题定时90分钟完整做一遍。这个阶段最重要的不是正确率而是时间分配和心态管理。如果一道题卡了15分钟还没思路我会直接看题解复盘自己的卡点在哪然后在错题本上记录下来。我不推荐一上来就刷大量难题。笔试中80%的题目其实是高频常规题只要基本功扎实那些题你都能拿分。与其钻研偏题怪题不如把常规题练到“闭眼能写”的程度。6.3 关于面试的延伸准备虽然标题叫“笔试”但如果你通过笔试进入面试环节算法方向的技术面试大概率也会围绕机器学习和算法题展开。字节跳动的面试风格是“连环追问式”的特别喜欢在你回答的基础上不断加深。比如你提到“用XGBoost建模”面试官可能会追问“XGBoost的叶子权重怎么求”“缺失值分裂策略是什么”“它和LightGBM在直方图算法上有什么区别”。所以我的建议是笔试复习阶段不要只盯“会做题”还要多问自己几个“为什么”。为什么这里用贪心而不是动态规划为什么AUC不受阈值影响为什么L1正则化更容易得到稀疏解这些问题的答案不仅面试有用在笔试简答题里也经常能作为加分细节写进去。6.4 简历和项目经历让你的算法功底“可视化”作为补充我想聊聊简历。笔试题目再难终究是“硬实力”的体现但简历上的项目和实习经历决定了你有没有机会坐到笔试考场里。2018年的字节跳动校招非常看重候选人的项目经历是否和算法方向相关。如果你当时没有正式的算法相关实习也没关系可以把自己做过的课程项目、竞赛经历、开源项目好好包装一下。重点不是写了多少行代码而是你解决了什么问题、用了什么方法、拿到了什么结果。比如“在某某竞赛中排名前5%”就比“熟悉机器学习算法”有说服力得多。如果你面试的是推荐、搜索、NLP方向最好能对“召回-粗排-精排-重排”这个整体架构有基本的了解。2018年字节跳动的信息流产品正在高速增长面试官会特别关注你对推荐系统的理解深度。笔试当然不会考这么宏观的问题但面试环节一定会涉及。6.5 最后再分享一个真实的小技巧备考期间我养成了一个习惯每次模拟笔试结束后都会用10分钟写“复盘笔记”记录这次模拟中考到的每个考点、自己做错的每道题的原因、以及下次要改进的点。这本笔记后来成了我最重要的复习资料比任何参考书都实用因为它完全针对我自己的薄弱环节。具体操作上我会把复盘笔记分成三栏题目简述、错误原因、改进措施。比如题目简述给定一个数组找到所有和为target的三元组。错误原因去重逻辑写错导致相同组合重复输出。改进措施双指针去重时要先跳过左指针的重复元素再处理右指针。这个习惯看上去很简单但坚持三个月后你会发现自己犯过的错误越来越少而且大多集中在少数几个特定类型上。针对这些高频错误做专项突破远比漫无目的地刷题高效。字节跳动2018校招算法方向第二批的笔试已经过去很多年但它的考察逻辑、重点方向、做题策略放到今天依然有很强的参考价值。数据结构、算法、机器学习基础、深度学习基础这四大块内容无论校招形势怎么变都是算法方向绕不开的基石。希望这篇复盘能帮即将参加算法校招的你少走一些弯路。