字节跳动秋招笔试复盘:算法与计算机基础的系统梳理

发布时间:2026/8/30 6:31:47
字节跳动秋招笔试复盘:算法与计算机基础的系统梳理 又是一年秋招季整理电脑里的旧文件时翻出了2017年的笔试记录。字节跳动那套秋招开发工程师笔试试卷是我当年印象最深的一套题。那时候字节跳动的校招规模还没现在这么大但笔试风格已经非常鲜明不堆冷门记忆题不考纯概念背诵而是把算法、数据结构和计算机基础揉在一起用几道题去测你的真实功底。这套卷子带给我的最大收获不是最后拿到什么结果而是它逼着我把操作系统、计算机网络、数据结构这些基础课重新过了一遍搞清楚了很多以前只记结论、不知道推导过程的东西。这篇文章就结合当时试卷的常见题型和核心考点聊聊我是怎么拆题、怎么分析、怎么踩坑的如果你是准备校招的应届生或者想系统巩固基本功的在职开发可以参考一下我的思路。1. 先看懂这套试卷2017秋招笔试到底在考什么1.1 试卷的大致构成与题量分布先说整体结构。字节跳动2017秋招开发工程师笔试是典型的在线OJ形式时长大约2小时。根据当时参加过的同学反馈和我自己的记录试卷大致由两部分组成一部分是客观选择题另一部分是编程题。选择题通常在20道左右覆盖数据结构、算法、操作系统、计算机网络、编程语言基础编程题一般是2到3道以算法题为主难度有明显梯度第一题偏热身后面逐渐加大难度。题型数量考查方向建议用时单选/多选20道左右数据结构、操作系统、网络、语言细节40分钟编程题2-3道哈希、滑动窗口、动态规划、拓扑排序等80分钟从题目安排能看出一个特点选择题考的是“你是否真正理解”编程题考的是“你能不能落成代码”。两者加在一起既筛理论功底薄弱的人也筛动手能力不足的人。需要说明的是校招笔试卷子通常不会原封不动对外流出所以我这里讲的是基于当时考生回忆和同类岗位常见考点的还原科目类型和考察思路是准确的具体题目细节属于综合归纳。这样梳理反而比死记一套原题更有价值。1.2 为什么是这个出题思路筛的不是背过面经的人很多人拿到这套卷子第一反应是“怎么没有XX框架的题怎么不考XXX技术栈”如果这么想就误解了笔试的目的。2017年字节跳动研发岗笔试的核心定位是——用最少的题目判断一个应届生在计算机基础上能打多少分。算法题之所以占比这么高是因为它在短时间内容易暴露一个人的思维习惯。你是直接写暴力解还是能快速想到优化方向你的代码边界处理是否严谨你对复杂度的敏感度如何这些能力靠背诵面经是装不出来的。选择题则负责检验基础知识点是否形成体系比如TCP的状态流转、操作系统的页面置换这些是写业务代码时不一定天天碰到、但出了问题必须能快速定位的东西。我当时做完这套卷子最大的感受是它不追求题目偏门而是把教科书中“看似简单”的知识点挖得很深。所以备考的核心不是海量刷偏题而是把每个高频考点的原理彻底弄明白。2. 选择题里的硬核基本功每一分都得有理有据2.1 数据结构与算法选择从二叉树到排序的经典陷阱选择题里数据结构占比很高而且经常出“看起来不难但容易错”的题。举个例子完全二叉树的题目反复出现。题目可能是一棵完全二叉树有n个节点问叶子节点有多少个。很多人凭感觉直接答n/2这是不对的。完全二叉树的叶子节点数需要根据最后一个节点的位置来判断。如果n是偶数最后一个分支节点是n/2叶子节点数是n/2如果n是奇数叶子节点数是(n1)/2。简单验证一下一棵只有1个节点的完全二叉树叶子数是1按奇数公式算就是(11)/21一棵有2个节点的完全二叉树叶子数是1按偶数公式算就是2/21。如果你只是背公式换个参数就可能搞混。我在考场上就因为这题犹豫了很久后来发现关键不是背公式而是去理解“完全二叉树按层序遍历编号后父节点和子节点的编号关系”编号为i的节点左孩子是2i右孩子是2i1。知道了这个关系很多二叉树题目都能现场推出来不需要背结论。排序算法也是选择题的重灾区。快速排序在什么情况下退化到O(n^2)当时卷子上有一道题问对已经有序的数组使用快速排序且每次都以第一个元素作为枢轴时间复杂度是多少。答案是O(n^2)因为每次分区都极度不平衡一侧为空递归深度达到n。这个知识点本身不难但它背后牵出一个工程实践问题生产中绝不会用这种朴素快排而是会用三数取中、随机化枢轴、小区间插入排序等优化手段。这些在笔试卷子里不一定直接考但如果你理解到位遇到“如何优化快排最坏情况”这种问答就能答得比别人深入。注意选择题里一旦出现排序、二叉树、哈希相关的题目先确认题目给定的约束条件比如“最坏情况”“平均情况”“稳定/不稳定”。这些限定词往往才是真正的考点。2.2 计算机网络与操作系统背八股翻车的重灾区网络和操作系统选择题是拉开分差的地方。因为这两块知识点多而杂很多人复习的时候靠“背题”结果换个问法就露馅。TCP四次挥手是我记忆中几乎必考的内容。题目不是简单问“有几个阶段”而是问主动关闭方在发送最后一个ACK之后进入什么状态这个状态要持续多久为什么答案是TIME_WAIT持续2MSL最大报文段生存时间原因是确保最后一个ACK能到达对方如果丢失可以重传同时让旧连接中的延迟报文段自然消失避免干扰新连接。很多人只记住了TIME_WAIT这个名字却说不清为什么是2MSL。这个问题如果换成“为什么主动关闭方要等2MSL而不是1MSL”估计能筛掉一半人。操作系统部分页面置换算法很常考。LRU和FIFO的缺页次数对比是经典题目。但2017年这套卷子更深入一点我记得有一道题是关于LRU算法在实际实现中用什么数据结构哈希表加双向链表哈希表负责O(1)查找双向链表负责O(1)删除和移动。这其实已经是在考察“你是否知道算法落地时怎么设计”。如果你只是背了LRU的概念这题就没办法靠猜。还有一道关于进程和线程的题也让我印象很深进程和线程在哪些资源上是共享的哪些是独立的线程共享进程的地址空间、文件描述符、信号处理器等但每个线程有自己的栈空间和寄存器上下文。这题的干扰项通常会设置成“线程拥有独立的地址空间”这是错的。只要理解了“线程是调度的基本单位进程是资源分配的基本单位”这题就不会错。2.3 语言与工程基础边界、内存、异常处理2017年的Java/C岗位笔试试卷里还会涉及一些语言细节题。比如Java的HashMap在JDK 1.8中是如何解决哈希冲突的答案是链地址法加红黑树当链表长度超过8且数组容量大于等于64时转化为红黑树。这个题到今天仍然是高频考点。C方向则喜欢考析构函数为什么通常声明为虚函数。因为当基类指针指向派生类对象delete操作时如果析构函数不是虚函数就不会调用派生类的析构函数导致资源泄漏。这个知识点考察的是“多态在析构场景下的应用”。这类型题目对工程经验不丰富的应届生来说有一定难度所以我的建议是不要在语言细节上花太多时间死磕而是把最常见、最影响实际开发的点弄透。比如数组越界、内存泄漏、空指针这些一定要能说出“为什么危险”和“如何避免”。3. 编程题解析从暴力解到最优解的全过程3.1 编程题出题风格看似熟悉实际全是套路字节跳动2017秋招笔试的编程题给我的整体感觉是题目背景简单没有复杂的业务场景但解法有层次暴力解能拿部分分最优解需要动脑子。常见的题型包括哈希表应用类找两数之和、判断是否存在重复元素。滑动窗口类最长无重复子串、最小覆盖子串。动态规划类最长递增子序列、编辑距离、背包问题变体。图论类拓扑排序、单源最短路。考场上时间有限不可能每道题都从零开始推导。所以我自己做题有个固定流程先看数据范围判断该用O(n^2)还是O(n log n)还是O(n)再看能不能用双指针或哈希表降低复杂度最后才考虑动态规划。如果数据范围是10^5就基本别想O(n^2)的暴力解了。3.2 完整推导滑动窗口求最长无重复子串这道题可以算是这类笔试的经典常客。题目描述是给定一个字符串s找出其中不含有重复字符的最长子串长度。最直观的解法是暴力枚举所有子串逐个检查是否有重复字符。时间复杂度O(n^3)或者O(n^2)取决于检查方式。这个解能拿一点分但肯定不是出题人想要的。优化思路是用滑动窗口加哈希集合def lengthOfLongestSubstring(s: str) - int: char_set set() left 0 res 0 for right in range(len(s)): while s[right] in char_set: char_set.remove(s[left]) left 1 char_set.add(s[right]) res max(res, right - left 1) return res这个解法的时间复杂度是O(n)空间复杂度是O(min(n, 字符集大小))。思路其实很简单右指针不断向右扩展把字符加入集合一旦发现当前右指针指向的字符已经在集合里就移动左指针把左指针对应的字符移除直到集合里没有重复字符。每次更新子串长度。我当时第一次做这道题时写的是暴力解能跑通小数据但遇到长字符串就超时。后来整理错题才理解了滑动窗口的精髓它利用了子串连续性这个特性让左右指针都不回退所以总移动次数是2n复杂度是线性的。如果面试官继续追问“能不能再优化”可以提“用哈希表记录每个字符最近一次出现的位置”这样左指针可以直接跳到重复字符的下一个位置不需要逐格移动def lengthOfLongestSubstring(s: str) - int: index_map {} left 0 res 0 for right, ch in enumerate(s): if ch in index_map and index_map[ch] left: left index_map[ch] 1 index_map[ch] right res max(res, right - left 1) return res这个版本在重复字符较少时效率更高而且代码简洁很多。笔试和面试中能主动给出这个优化版本会是一个加分项。注意滑动窗口类题目的关键是“窗口内元素满足某个条件”。如果条件不满足就缩小窗口满足就尝试扩大窗口。搞清楚左指针什么时候移动、移动多少比死记代码重要得多。3.3 完整推导拓扑排序加字典序优先的变体另一道让我印象深刻的编程题是一道任务调度题。大意是给定N个任务和M个依赖关系每个依赖关系表示某个任务必须在其前置任务完成后才能执行要求输出一种合法的任务执行顺序。如果有多种合法顺序要求输出字典序最小的那个。这个题的本质是有向无环图的拓扑排序。最标准的解法是Kahn算法先统计每个节点的入度把入度为0的节点加入队列每次从队列中取出一个节点输出然后把它的所有后继节点入度减1如果后继节点入度变为0就加入队列。这个过程持续到所有节点都输出为止。如果最终输出节点数小于N说明图中有环不存在合法的拓扑序。但加上“字典序最小”这个条件后就不能用普通队列了需要用优先队列最小堆#include vector #include queue #include functional using namespace std; vectorint topoSort(int n, vectorvectorint edges) { vectorint indegree(n, 0); vectorvectorint graph(n); for (auto edge : edges) { int a edge[0], b edge[1]; graph[a].push_back(b); indegree[b]; } priority_queueint, vectorint, greaterint pq; for (int i 0; i n; i) { if (indegree[i] 0) pq.push(i); } vectorint result; while (!pq.empty()) { int cur pq.top(); pq.pop(); result.push_back(cur); for (int nxt : graph[cur]) { indegree[nxt]--; if (indegree[nxt] 0) pq.push(nxt); } } if ((int)result.size() ! n) return {}; return result; }为什么用优先队列因为普通队列是先进先出只能保证按下标或入队顺序输出没法保证字典序。而最小堆每次取当前可选任务中编号最小的那个就能在“拓扑序合法”的前提下做到字典序最小。这里的复杂度是O((NM)logN)其中N是任务数M是依赖关系数logN来自优先队列的调整开销。这道题给我的启发是很多算法题都是在经典算法的基础上加一个约束条件本质上是看你能不能把经典算法灵活改造。如果你只背过裸的拓扑排序没理解队列在这里的作用遇到“字典序最小”就慌了。这也是为什么我一直强调学算法要理解思路不是记住代码。4. 笔试现场的经验与翻车记录4.1 时间分配选择题别恋战我当年第一次做在线笔试时犯了一个经典错误在选择题上耗了太多时间。有一道网络题我拿不准反复纠结了快10分钟结果后面编程题时间不够第一题写完暴力解第二题只写了一半。后来我总结的考场原则是选择题每题最多2分钟没有思路就先标个最可能的答案跳到下一题。编程题才是拿分的大头一道完整的最优解抵得过好几道纠结的选择题。合理的安排是拿到卷子先花3分钟把所有题目扫一遍对编程题的难度有数——哪题是热身、哪题是大题。然后先把简单编程题解决再回头做选择题最后集中火力攻难题。因为选择题是一锤子买卖做完了不会因为你后面想起来改答案而自动加分但编程题多写几个测试用例验证一下能大幅提高通过率。4.2 读题与边界条件AC不了往往不是算法问题在线笔试平台对编程题的评判是全自动的任何边界条件没处理好都会导致运行错误或超时。我见过太多人算法思路对了却因为没考虑空输入、没处理数据越界、没注意整型溢出而丢掉大量分数。几个常见的边界坑空数组、空字符串很多解法在输入为空时会报错必须单独处理。数组越界循环里访问nums[i1]前要先判断i1是否在范围内。整数溢出两个很大的int相加可能溢出该用long long的地方别省。图可能不连通拓扑排序、DFS时需要检查所有节点是否都被访问过。输入可能有重复数据哈希表的插入和查找要留意覆盖关系是否正确。我在写完每道编程题后都会花1分钟跑几个特殊用例空输入、全相同元素、最大数据量的情况、只有一个元素的情况。这个习惯让我避免了好几次无谓的扣分。4.3 复盘与一题多解面试官真正想听的是什么笔试结束后的复盘比刷题本身更重要。我当时花了几个晚上把每道错题重新推导了一遍尤其是那些“看答案能看懂、自己写就卡壳”的题目我会关掉答案重新写直到能流畅完成。还有一个提升很大的习惯对同一道题尝试多种解法。比如最长无重复子串那题先写暴力解再写滑动窗口再写哈希表优化版。这个过程的收获不是“多记了一个解法”而是理解了不同解法之间的复杂度差异从何而来以及什么场景下应该选择哪一种。笔试之后如果通过面试官很可能会围绕笔试题追问——为什么这么解能不能再优化有没有其他思路如果只是背答案这一环节很容易露怯。5. 2017年的试卷对今天校招复习的参考价值5.1 从2017到如今的笔试趋势变化七八年过去校招笔试题型一直在变但底层逻辑没变。现在的算法题难度整体有所提升题目场景也更丰富比如开始结合大数据处理、流式计算等背景。但核心还是那几类数据结构、搜索与图论、动态规划、字符串处理。2017年这套试卷中暴露出的“重基础、重推导、重边界”导向到今天依然适用。另外现在很多公司在线笔试平台会实时记录你的代码编译次数、测试用例通过率甚至有时长指标。这意味着“一次编译通过”逐渐成为加分项。怎么提升一次通过的准确率就是靠平时写题时的严谨性不要依赖编译器反复帮你查错。5.2 如何借这套题做系统复习如果你想用这套题来检验自己的基本功我建议按下面这个顺序过一遍数据结构数组、链表、栈、队列、哈希表、树、堆、图每一个都要知道典型操作的时间复杂度。算法排序、二分、双指针、滑动窗口、DFS/BFS、回溯、动态规划、拓扑排序、最短路。计算机基础TCP/UDP、进程线程、内存管理、文件系统。语言基础你用的主力语言的核心容器类底层原理、内存管理机制。每过完一个小模块就找10到15道对应类型的题做专项训练不看题解先独立思考30分钟实在没有思路再看解析。然后用表格记录每道题的错因是思路问题、边界问题还是语法问题。这样做一周左右再拿一套模拟题做整体测试你就能明显感觉到自己的变化。这几年我陆陆续续参与过一些新人面试发现一个现象很多候选人在算法刷题上投入了大量时间但问他“为什么用哈希表而不用数组”“为什么这个解法是O(n)”时却说不出所以然。反而是那些能把一道经典题从暴力解到最优解讲得清清楚楚的人哪怕笔试分数不是最高最终通过率却很高。归根结底面试官想找的不是一个刷题机器而是一个有扎实基本功、能解决实际问题的人。回看2017年那套笔试试卷它给我最大的启发就一句话基础不牢地动山摇。不管技术栈怎么换、框架怎么变操作系统、网络、数据结构、算法这些底层能力永远是最值得投入时间的部分。如果你正准备秋招与其焦虑题目难不难、通过率高不高不如静下心来把每一个高频考点的原理搞透把每道经典题从暴力解到最优解完整推一遍。这套功夫下去了分数是水到渠成的事。