
2019年秋季那波校招B站是很多同学盯了很久的目标。喜欢追番、刷弹幕想着有一天能去写视频网站的后端代码把爱好直接变成工作。我身边好几个朋友都在那年投过B站的开发岗回来之后把面试遇到的编程题汇总成了一份文档。后来我自己也把这份合集从头刷了一遍发现即使是隔了几年再拿出来看里面的题依然很有嚼头。原因是B站技术面的风格一直比较稳考的东西不追求偏怪难而是把计算机基础、算法和工程意识揉在同一套题里。这份2019秋招编程题合集整理下来大约二三十道题覆盖了数组、字符串、链表、二叉树、动态规划还有少量系统设计题。它适合三类人第一类是正在准备校招和实习的应届生想提前感受B站面试的难度和考法第二类是工作几年想跳槽的工程师拿它当算法手感恢复的燃料第三类是单纯想练基本功的人把题目当题库做顺便观察视频网站业务的工程诉求。无论哪一种只要你能把这份题吃透收获的绝对不只是“会做几道题”而是对面试官出题逻辑的把握。1. 为什么一份2019年的秋招题现在还值得翻1.1 秋招真题的价值不在“旧”而在“稳”很多人一听是2019年的题第一反应是“都过去这么久了题型肯定变了吧”。实际上技术面试的底层考察点更新速度远比技术栈迭代慢得多。2019年考滑动窗口、最长上升子序列、二分查找边界现在校招依然在考只是包装成不同的故事背景。B站这家公司的题目更有代表性因为它的业务场景包含视频上传、弹幕、推荐、搜索这些场景天然适合出算法题和工程题所以题目内容和日常工作贴合得非常紧。我刷这份题的时候有个明显感受题目不搞“脑筋急转弯”也没有故意加一堆刁钻限制来卡人。它更像是在考察一个工程师面对真实问题时的思考路径——能不能先给暴力解能不能分析出瓶颈能不能在提示下一步步优化到最优解。这种风格恰恰是校招面试里最值得提前适应的因为你平时在OJ上刷题往往直接奔着最优解去但现场面试要求的是“思考过程可见”。所以这份合集不是一个过期的题库而是一套稳定的能力标尺。它帮你检验的是你对基础数据结构和经典算法的熟练度以及在压力下能不能把思路讲清楚、把代码写对。1.2 这份合集适合谁去刷先说应届生。校招准备最怕的就是“刷题方向跑偏”花大量时间钻研冷门数据结构结果面试官问的都是高频基础题。用这份合集做定位你能快速知道B站这类视频互联网公司喜欢考什么以及每题大概是什么难度。建议是先独立做一遍再对照题解复盘而不是上来就看答案。再说想跳槽的工程师。工作几年之后很多人算法手感明显退化不是说不会而是手生。拿这份题当“恢复训练”很合适题目量不大难度梯度合理每天两三道一周左右就能找回状态。尤其是里面的工程场景题对社招面试更有参考价值因为社招本来就更看重系统设计能力和业务理解。如果你只是单纯想练基本功这份合集同样值得刷。它能帮你把字符串、数组、动态规划、二叉树这些核心板块串起来形成一套完整的解题框架。我甚至建议你把它当“自测卷”用限定两小时做完检验自己哪个板块最薄弱。2. 题型全景与考察重点拆解2.1 从题型分布看B站技术偏好我根据当年收集到的面经和自己的刷题记录把这份合集中的题目按类型做了个粗略统计。虽然每一年题目会有微调但整体分布相当稳定。题目类型题量占比常见考点我的判断字符串处理20%左右滑动窗口、子串匹配、字符串模拟出镜率最高几乎必考数组与二分20%左右双指针、二分边界、前缀和性价比极高容易拿满分链表与栈15%左右反转链表、单调栈、LRU思想考察代码基本功二叉树10%左右层序遍历、最近公共祖先、路径问题中规中矩但必须熟练动态规划15%左右最长上升子序列、编辑距离、背包变体拉开差距的核心板块工程场景设计10%左右限流、断点续传、缓存策略结合业务考察综合能力其他杂项10%左右排序、位运算、随机数作为调剂和加题这个分布透露出的信息挺直接B站不喜欢考特别复杂的图论和高级数据结构而是把重心放在“工程师日常最常用的算法”上。字符串和数组为什么占比高因为视频网站到处都是文本处理、搜索词匹配、播放状态判断这些场景天然和字符串、数组强相关。动态规划占比不低是因为它最能考察一个人的逻辑推导能力和状态抽象能力。2.2 各模块知识点权重分析如果按“是否必须拿分”来给这些知识点排个序我的建议是这样的第一优先级滑动窗口、双指针、二分查找、链表反转、层序遍历。这些题属于“背也要背熟”的品类因为它们解法固定、套路清晰只要练过就一定能写对。现场如果栽在这里面试官对你的印象会大打折扣。第二优先级动态规划、单调栈、前缀和、最近公共祖先。这些题需要一定的分析能力但题型有限可以在短期内有针对性地突破。尤其是动态规划建议把常见的几种状态定义方式吃透比如“以i结尾”“前i个元素”“区间[i,j]”大部分题都能往这几个模子里套。第三优先级系统设计、工程场景题。这类题没有标准答案但反而最容易提前准备。你只需要理解常见业务的通用方案再结合B站的特点说出来就行。后面第四章我会专门展开讲弹幕限流和断点续传这两个高频场景。3. 算法题精讲三道必会的核心题3.1 无重复字符的最长连续子串——滑动窗口的标准姿势这道题在2019年B站后端岗的现场面试里出现过基本可以算视频网站面试的“保留曲目”。题目描述非常简洁给定一个字符串s找出其中不含有重复字符的最长连续子串的长度。比如s abcabcbb答案是3对应的子串是abc。看起来简单但想一次性写对需要想清楚左右边界怎么移动。最笨的办法是枚举所有子串再逐个检查是否有重复字符时间复杂度O(n^3)基本属于“说出来会被立刻打断”的方案。稍微好一点的是用两个for循环枚举起点和终点借助一个set判断重复复杂度O(n^2)。但面试官真正想听的优化方案是滑动窗口把时间复杂度压到O(n)。用Python写出来是这样的def lengthOfLongestSubstring(s: str) - int: last {} left 0 res 0 for right, ch in enumerate(s): if ch in last and last[ch] left: left last[ch] 1 last[ch] right res max(res, right - left 1) return res核心逻辑只有两句话遇到重复字符时把左边界跳到该字符上一次出现位置的下一个然后更新当前字符的最新位置同时维护最长长度。很多初学者会问为什么判断条件是last[ch] left而不是last[ch]存在就行因为如果这个字符上一次出现的位置已经在当前窗口左边了说明它不在窗口内不需要移动左边界。这个细节最容易踩坑不加上会报错。我当年第一次写这道题就是把left直接改成last[ch] 1结果窗口直接跳过了正确答案。后来才意识到必须保证“上一次出现位置在当前窗口内”才需要收缩左边界。这道题测试时还要注意空字符串和单字符这两个极端情况空串返回0单字符返回1。3.2 最长上升子序列——从O(n²)到O(n log n)最长上升子序列LIS是动态规划里的经典题B站2019秋招也考过。题目是给定一个无序整数数组nums求最长的严格递增子序列长度。注意是子序列不是连续子数组所以元素可以不连续。比如nums [10,9,2,5,3,7,101,18]答案是4对应子序列[2,3,7,101]或者[2,5,7,101]。这道题有两条路线。第一条是朴素动态规划定义dp[i]为以nums[i]结尾的最长上升子序列长度。初始化时每个dp[i]都至少是1因为单个元素本身可以看作长度1的上升子序列。然后遍历i之前的所有j只要nums[j] nums[i]就用dp[j] 1去更新dp[i]。最后答案就是dp数组里的最大值。def lengthOfLIS(nums): n len(nums) if n 0: return 0 dp [1] * n for i in range(n): for j in range(i): if nums[j] nums[i]: dp[i] max(dp[i], dp[j] 1) return max(dp)这个写法时间复杂度O(n^2)空间复杂度O(n)。面试时先给出这个版本能让面试官看到你的基础DP能力然后再提优化。第二条路线是贪心加二分把时间复杂度降到O(n log n)。这里维护一个tails数组tails[i]表示长度为i1的上升子序列中末尾元素的最小值。遍历每个数字x用二分查找在tails里找到第一个大于等于x的位置替换掉它如果x比tails末尾还大就追加到末尾。最后tails的长度就是答案。import bisect def lengthOfLIS(nums): tails [] for x in nums: pos bisect.bisect_left(tails, x) if pos len(tails): tails.append(x) else: tails[pos] x return len(tails)这里有个很多人容易搞混的点tails数组里存的并不是真正的最长上升子序列它只是一个“末尾最小值”的维护结构。比如nums[4,5,6,1,2,3]tails最终会是[1,2,3]长度是3但真实的最长上升子序列可以是[4,5,6]长度也是3。你只能用tails的长度不能用它的内容。另外题目要求严格递增所以用bisect_left如果改成非递减就不得不换成bisect_right这是非常隐蔽的一个坑。3.3 旋转数组最小值——二分边界怎么卡才不翻车旋转数组最小值这道题B站2019秋招里也出现过。题目说一个升序排列的数组把前面若干元素搬到末尾形成旋转数组。要求找到数组中的最小元素。比如[4,5,6,7,0,1,2]输出0原数组没有重复元素。这题看似是二分但和普通二分找目标值不同它找的是“分界点”和“最小值”。标准解法是维护left和right每次取mid比较nums[mid]和nums[right]的大小关系。如果nums[mid] nums[right]说明最小值在右侧区间left mid 1否则说明最小值在左侧区间或就是midright mid。循环结束条件用left right最后返回nums[left]。def findMin(nums): left, right 0, len(nums) - 1 while left right: mid (left right) // 2 if nums[mid] nums[right]: left mid 1 else: right mid return nums[left]为什么和nums[right]比较而不是和nums[left]比较这是这道题最关键的考点。如果nums[mid] nums[left]并不能确定最小值在哪边。比如[4,5,6,7,0,1,2]mid对应的是6nums[mid] nums[left]6大于4但最小值在右边。所以和left比较很容易被边界条件误导。而nums[right]在升序数组中一定是“区间内最大可能的锚点”只有当右半段发生了旋转nums[mid]才会大于nums[right]这是一种唯一且确定的条件。另外补充一点如果允许重复元素比如[2,2,2,0,1,2]这种当nums[mid] nums[right]时怎么处理做法是让right - 1一个一个地收缩右边界让左指针逼近最小值。代价是极端情况下时间复杂度退化为O(n)。面试时如果被追问重复元素的情况能答出这层基本就过关了。4. 工程场景题从B站业务反推设计题4.1 弹幕高频写入如何限流工程场景题在B站2019秋招里虽然占比不算高但一旦出现很容易和具体业务绑定。最典型的一道是某热门视频刚上线弹幕量瞬间暴涨弹幕网关该怎么设计限流才能保证服务不挂。这个题没有唯一答案但考查的核心点是限流算法和分布式场景意识。我推荐按下面这个层次来回答单机层面可以先用令牌桶或漏桶算法。令牌桶的实现思路是系统以固定速率往桶里放令牌每个请求需要取走一个令牌才能被放行桶满则丢弃令牌。突发流量时只要桶里有令牌就能通过从而允许一定的突刺同时整体速率又是可控的。漏桶则是将请求排队以固定速率处理擅长平滑流量但对突发流量的容忍度低。两者选哪个取决于产品要“保吞吐”还是“保平滑”。由于弹幕网关通常不止一台机器单机限流远远不够必须考虑分布式限流。常见做法是用Redis Lua脚本实现原子计数。比如滑动窗口或固定窗口计数器每次请求执行一段Lua脚本判断当前窗口内的请求数是否超过阈值。Lua脚本的好处是原子性多个并发请求不会出现“检查完还没来得及加一另一个请求也通过了”的超卖问题。伪代码大概是这样的思路local key KEYS[1] local limit tonumber(ARGV[1]) local current tonumber(redis.call(GET, key) or 0) if current limit then return 0 else redis.call(INCR, key) redis.call(EXPIRE, key, 60) return 1 end答到这里面试官一般会顺着追问“限流之后流量去哪了”。这时候要给出削峰填谷的思路被限流器拦下来的弹幕可以进入消息队列由下游消费者按固定速率消费并写入存储。这样即使瞬时弹幕量是平时的一百倍数据库也不会被打爆。最后再补一句降级策略当队列积压到一定水位时优先丢弃明显重复或低价值的弹幕保证核心互动体验稳定。这套回答讲下来覆盖面就很完整了。4.2 断点续传方案如何设计B站是视频网站用户上传视频内容是一个非常核心的场景。网络不稳定导致上传中断要求支持断点续传这也是当年出现过的工程题。我的回答框架分四层。第一层分块上传。前端在上传前把文件切成固定大小的分片比如每片4MB每个分片独立上传。这样中断后只需要从失败的那一片开始而不是整个文件重传。第二层状态记录。后端需要记录每个分片的上传状态可以用一张数据表或RedisKey是上传会话IDValue是已上传分片的编号列表。前端每次上传前先向后端查询哪些分片已经上传成功跳过这些分片。第三层分片校验。每个分片上传完成后服务端返回该分片的MD5或CRC32值前端比对结果确认是否成功。全部上传完成后后端再按分片顺序合并文件。合并时要注意磁盘空间和文件完整性校验通常会对整个文件再做一次MD5或者使用记录总大小的方式确认没有缺失分片。第四层断点恢复。当网络恢复或用户重新打开页面时上传组件重新发起查询拿到已上传分片列表从下一个分片继续。如果有分片损坏或没上传完整就只重传那一片。设计里能体现“业务理解”的点是要聊到视频文件通常很大的特点以及转码流程对文件完整性的要求。如果上传的是原始视频文件分片合并后还要先校验再进转码队列避免转码到一半发现文件损坏浪费大量计算资源。答出这个层次面试官会认为你真的思考过视频上传链路而不是只会背概念。5. 刷题复盘的路线图5.1 三个月冲刺节奏如果你现在离秋招还有大约三个月这份合集可以按三阶段来用。第一个月主攻基础数据结构。每天安排2到3道数组、字符串、链表、栈和二叉树题目把每一类题型的固定套路练熟。这个阶段不追求难题但要确保每道题都能独立写对并且能说出时间复杂度和空间复杂度。第二个月专题突破算法重点。动态规划、二分、滑动窗口、双指针这四个专题是B站这类公司的高频考区。每天做一个专题建议配合同类题目集中练习。比如今天专做动态规划就连续做五道不同状态定义方式的DP题而不是一天换一个知识点。集中练习的好处是你能在短时间内建立“这类题长什么样”的直觉。第三个月进入模拟面试阶段。拿合集中的题目限定45分钟一题完整走一遍读题、确认边界条件、说暴力解、再优化、手写代码、跑测试用例。这个过程要严格模拟现场不能一边写一边查资料。我推荐的节奏是上午一套完整模拟下午复盘错题晚上补弱项。每周再做一次全量回顾把每个专题的解题套路写在纸上能默写出来才算真的掌握。5.2 每道题都应该有的三遍复盘法很多同学刷题有一个通病做完一道题看一眼AC了就过第二天全忘光。我建议把每一道题至少做三遍。第一遍限时独立完成。如果45分钟没思路直接看题解不要硬耗。看完题解之后必须关掉答案自己重新写一遍代码。第二遍隔一天再独立做。这时候你对题目的记忆已经在消退能做出来才说明你真正理解了解法而不是背下了代码。第三遍隔三天后只做“思路复述”。在纸上写下这道题的解法关键词、时间复杂度、边界条件不看代码直到能清楚讲给一个虚拟面试官听。这个方法看起来很笨但坚持下来效果非常好。我刷这份合集中的动态规划题时第一遍能写出来的不到一半但三遍之后几乎所有题都能在15分钟之内出思路。不要贪快每周严格按节奏复盘比打鸡血式刷一百道新题更有用。6. 现场答题技巧与高频翻车点6.1 时间分配和沟通顺序现场写代码和平时刷题完全不同你面对的是面试官而不是屏幕上的题目描述。很多基础不错的应届生因为没掌握表达节奏导致明明会做也被刷。我总结了一个比较稳的答题顺序。拿到题后先花1到2分钟把题目复述一遍同时主动确认边界条件。比如数组可能是空吗字符串包含哪些字符数据规模大概多大有重复元素吗这些信息直接决定算法选型。比如数据规模到10^5O(n^2)大概率超时你就要优先想O(n log n)的方案。然后先讲暴力解。哪怕你知道更优解也建议先把暴力思路一两句话说完再过渡到优化方案。这样面试官能看到你“先保证正确再追求效率”的工程思维。如果你一上来就写最优解一旦卡壳连兜底方案都没有。讲完思路之后再动手写代码。写之前把时间复杂度和空间复杂度说清楚写的过程中边写边解释关键变量。写完以后主动提出跑一个测试样例逐步检查。整体时间分配建议读题2分钟思考5分钟讲述思路2分钟写码15分钟测试和优化5分钟。大部分算法题控制在30分钟内完成比较理想。如果超过15分钟还没想到优化思路就直接把暴力解写出来至少能保证有产出。6.2 常见问题速查表刷题和面试反复踩坑之后我整理了下面这张速查表基本覆盖了算法面试里最高频的翻车点。问题现象常见原因解决方案滑动窗口left跳过头误把重复字符上一次出现位置直接当left加上“上一次位置 当前left”的判断DP数组初始值全是0没有意识到单个元素本身是合法子序列子序列类DP初始值大多设为1二分陷入死循环right更新写成mid-1缩小了本该包含答案的区间“找最小值”场景right mid条件用left right递归层数过深导致栈溢出二叉树深度大递归压栈超限改成迭代写法或显式用栈模拟写代码前没确认数据规模误用O(n^2)方案导致超时先问规模再定复杂度级别现场一紧张忘了API对语言内置库不熟提前记好常用API如Python的bisect、collections.deque只会背模板无法解释思路平时刷题靠记忆没有理解原理每道题强迫自己口头复述一遍解法链表题忘记处理头节点链表操作边界没想清楚统一引入dummy头部节点简化边界判断其中“链表题忘记处理头节点”属于我见过最可惜的翻车。明明会做反转链表就因为没加dummy节点最后循环条件写错整道题崩盘。这类问题靠天赋解决不了只靠大量练习把边界条件变成肌肉记忆。从复现角度来看我特别建议你把每道题的核心代码摘出来按专题分类整理形成自己的题典。考试前看题典里自己总结的易错点比临时翻题解效率高得多。准备校招的冲刺阶段时间比什么都珍贵学会做减法、抓高频点才能把努力变成分数。我个人在实际准备过程中还有一个体会刷这份合集不只是为了通过某一场面试。里面的每一道题都像一面镜子能照出你代码能力的短板。你把这份题刷透之后再回头看日常项目里的代码思考方式会有明显变化——遇到问题先想边界条件再做复杂度评估然后才动手实现。这个习惯远比多背几道题更有价值。