尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

B站算法岗秋招笔试题复盘:从KMP到动态规划的核心考点

B站算法岗秋招笔试题复盘:从KMP到动态规划的核心考点 2019年B站秋招技术岗的算法笔试题第二套当年在求职群里讨论度很高。很多人做完第一反应是选择题怎么这么杂KMP、排序、机器学习、图像处理全都掺在一起编程题反而比较常规。我后来把整套题认真复盘了一遍发现这套题其实是B站算法工程师日常工作的缩影——既有纯算法基础也有业务场景理解还有工程实现能力。这套题适合两类人看一类是正在准备大厂算法岗校招的同学可以直接把它当模拟卷做另一类是已经在做推荐、搜索、音视频相关算法的工程师也能从中看到面试官真正想考察的底层能力。接下来我会按实际做题顺序把考察方向、高频考点、编程题思路、业务场景题以及我踩过的坑一条条拆开讲清楚。1. 这套题到底考什么整体风格与考察方向1.1 选择题出题范围比想象中要宽B站的算法岗位不是单一的“推荐算法工程师”还有内容理解、视频理解、图像算法、音频算法、搜索算法等方向。所以笔试题覆盖面会刻意拉得比较宽。这也是为什么很多人觉得第二套题“杂”。从题目类型看选择题大致可以分成四块数据结构与基础算法KMP的next数组、堆排序稳定性、快速排序复杂度、贪心算法判断、Dijkstra适用条件、快速幂等。机器学习与深度学习基础KNN属于什么学习方式、K-means的收敛特性、KL散度是否对称、粒子群优化思路、卡尔曼滤波的预测-更新两步等。图像与音视频基础Sobel算子检测什么、音频重采样解决什么问题、图像分类任务的基本流程等。业务场景题搜索相关度怎么算、推荐排序怎么设计、如何评估一个内容理解模型等。这里有一个很重要的信号B站对算法岗的期待不是“会调包”而是真正理解算法原理并且能把算法落到视频业务场景里。比如考KL散度表面是在考数学实际是希望你理解生成模型、变分推断里的核心概念因为做视频内容理解或者用户行为建模时这些概念会反复出现。1.2 编程题命题风格经典题为主坑在细节第二套题的编程题是两道难度中等偏上类型上以字符串和动态规划为主偶尔会来一道图论题。整体风格就是不考偏题怪题但会在边界条件和优化思路上埋坑。在线笔试环境一般是牛客网或者赛码网需要自己处理输入输出。这一点看着不起眼实际考试时影响很大。我记得当年有人第一道字符串题用getline读入结果本地跑得好好的一提交就超时或者读不到数据原因就是没注意平台的语言环境和输入格式差异。选择题覆盖面广编程题偏基础这两者叠加起来其实是在考察一件事基础是否扎实到能稳定输出。2. 高频基础算法考点这些题错过一次就长记性2.1 KMP算法与next数组字符串匹配的地基第二套题里有一道非常典型的KMP选择题题干是对于模式串 p abacaba其 next 数组是什么很多同学看到这种题就慌其实KMP的next数组计算有固定套路只要理解“最长相同前后缀”这个定义就能一步步推出来。先说明一下next数组的不同定义。有些教材把next[i]定义为模式串前 i1 个字符组成的子串中最长相同前后缀的长度。有些版本则定义为失配时模式串指针应该跳转到的下标。两种定义下结果不同但推导逻辑一样。B站这套题我印象里用的是“最长相同前后缀长度”这个定义也就是前缀表中常见的形式。以 p abacaba 为例从左到右逐个子串计算下标i子串最长相同前后缀next[i]0a无01ab无02abaa13abac无04abacaa15abacabab26abacabaaba3所以按这个定义next数组是 [0, 0, 1, 0, 1, 2, 3]。如果题目定义的是“失配跳转位置”那通常会在next[i]基础上再整体左移或减1这个时候就要格外小心必须先看清题目给出的定义再作答。这道题考察的不只是记忆而是是否真的理解“前后缀匹配”这个核心思想。实际业务里B站的搜索联想词、弹幕敏感词过滤、稿件标题匹配等场景都会用到字符串匹配KMP不是纸上谈兵。实操建议考前不要死记next数组的例子动手把模式串的各个前缀子串写出来自己标一遍最长相同前后缀比背十遍都管用。2.2 排序算法全家桶稳定性和复杂度不能只背结论第二套题有一道选择题问下列排序算法中哪些是不稳定的选项里通常会有快速排序、堆排序、归并排序、直接插入排序。这道题的正确解法不是靠“死记结论”而是理解“稳定性”到底指什么——相同元素的相对顺序在排序前后是否保持不变。直接说结论排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性冒泡排序O(n^2)O(n^2)O(1)稳定直接插入排序O(n^2)O(n^2)O(1)稳定简单选择排序O(n^2)O(n^2)O(1)不稳定快速排序O(nlogn)O(n^2)O(logn)不稳定堆排序O(nlogn)O(nlogn)O(1)不稳定归并排序O(nlogn)O(nlogn)O(n)稳定希尔排序取决于增量序列取决于增量序列O(1)不稳定基数排序O(d(nr))O(d(nr))O(nr)稳定堆排序为什么不稳定因为堆排序在“交换堆顶和末尾元素”时可能会把相同元素的相对顺序打乱。举个例子数组 [5a, 5b, 3]建堆后5a和5b可能交换位置排序结束后5a跑到5b后面去了这就是不稳定。我在考场上有一个习惯遇到排序稳定性的题直接在脑子里跑一个三个元素的例子比如 [2a, 2b, 1]想一下排序结束后2a和2b的顺序会不会变。这个方法比硬背表格可靠得多。堆排序本身的实现也要会。核心是“建堆 交换堆顶 向下调整”三步。笔试如果出编程题让你手写堆排序先把调整函数写对void heapify(vectorint arr, int n, int i) { int largest i; int left 2 * i 1; int right 2 * i 2; if (left n arr[left] arr[largest]) largest left; if (right n arr[right] arr[largest]) largest right; if (largest ! i) { swap(arr[i], arr[largest]); heapify(arr, n, largest); } } void heapSort(vectorint arr) { int n arr.size(); for (int i n / 2 - 1; i 0; i--) heapify(arr, n, i); for (int i n - 1; i 0; i--) { swap(arr[0], arr[i]); heapify(arr, i, 0); } }这里有一个隐蔽的坑建堆要从最后一个非叶子节点开始也就是 n/2 - 1而不是从0开始。如果这里写错建出来的堆不满足堆性质整个排序结果都会错。2.3 贪心、Dijkstra与动态规划怎么区分选择题里还常考一类题下列算法中使用了贪心策略的是选项一般有Dijkstra、Prim、Kruskal、动态规划、回溯等。贪心算法的核心是“每步都做当前看起来最优的选择并期望最终结果最优”。它成立的前提是问题具有贪心选择性质也就是局部最优能推出全局最优。比如活动选择问题、Huffman编码、找零钱问题里的某些情况。Dijkstra算法就是典型的贪心。它维护一个“已确定最短路径”的集合每次都从优先队列里取出当前距离源点最近的节点然后松弛它的邻接边。这个过程里一旦一个节点被确定就不再更新它——这正是贪心的体现。这里必须记住Dijkstra要求边权非负。为什么因为当某节点u被拿出优先队列时算法认定dist[u]已经是最短路径了如果后面出现一条负权边能绕到u并让dist[u]更小那这个认定就失效了。用负权边时得用Bellman-Ford或SPFA。动态规划和贪心的区别在于动态规划会保存子问题的解并考虑所有可能的选择路径贪心每步只做一种选择不会回头看。笔试常考的经典动态规划题包括最长上升子序列、最长公共子序列、背包问题、编辑距离等。B站这套题在选择题里会问“哪些问题适合用动态规划解决”本质上就是考对这两个概念的区分。2.4 快速幂数论题的“万能钥匙”快速幂在算法岗笔试里出现的概率很高尤其是要算组合数、概率期望的时候。B站这套题虽然没直接考很深的数论但快速幂属于“基本功”我建议每个人都能在30秒内默写出来。核心思路很简单把指数 b 看成二进制从低位到高位看如果当前位是1就乘上对应的 a 的幂次每次把 a 平方一次b 右移一位。这样时间复杂度从 O(b) 降到 O(log b)。long long fastPow(long long a, long long b, long long p) { long long res 1; a % p; while (b 0) { if (b 1) res res * a % p; a a * a % p; b 1; } return res; }几个容易踩的点底数 a 要先取模防止 a 很大时溢出。结果 res 初始化为1而不是0。指数 b 为0时循环不执行直接返回1这符合数学定义。如果 p 很大中间乘法可能溢出 long long这时需要换成更大范围的数据类型或者用快速乘。我当时第一次写快速幂就栽在“a a * a % p”这行上如果忘记对中间结果取模算到后面数据直接溢出结果完全不对。3. 机器学习与深度学习考点不做只会调包的算法岗3.1 KNN、K-means与聚类算法B站算法岗笔试里机器学习基础题不会考得很偏但非常爱考“概念对比”和“边界条件”。KNN和K-means是高频题目。KNN是监督学习还是无监督学习答案是监督学习因为它需要带标签的训练数据。KNN又被称为“惰性学习”因为它在训练阶段几乎不做任何事只是把样本存起来真正计算发生在预测阶段。预测时计算待预测样本与所有训练样本的距离取最近的K个邻居投票决定类别。KNN里的K是一个超参数。K选得太大会把距离很远的样本也拉进来造成欠拟合K选得太小容易受噪声影响造成过拟合。特征也需要标准化否则量纲大的特征会主导距离计算比如“年龄”和“年收入”放在一起收入数值可能把年龄的影响完全淹没。K-means则是典型的无监督聚类算法。它的流程是先随机选择K个点作为初始簇中心然后交替执行“分配”和“更新”两步直到中心点不再显著变化。这里的“K”需要预先指定而且初始中心选得不好可能会收敛到局部最优解。所以实际使用中一般会跑多次选目标函数最优的结果。选择题常问的点是K-means迭代过程中如果某个簇为空怎么办或者K-means对离群点是否敏感答案是对离群点敏感因为簇中心计算用的是均值离群点会把均值拉偏。3.2 从KL散度到模型评估KL散度Kullback-Leibler divergence在笔试题里出现的频率不低因为它和许多现代机器学习方法直接相关。题目往往不会让你手算复杂的公式而是考概念性质。KL散度的定义是KL(P || Q) Σ P(x) * log(P(x) / Q(x))它衡量的是用分布Q去近似真实分布P时额外需要多少信息量。KL散度有两个很关键的性质非负性KL(P || Q) 0当且仅当P和Q相同或几乎处处相同时取0。非对称性KL(P || Q) 一般不等于 KL(Q || P)。非对称这一点是选择题的高频考点。很多人凭直觉觉得“差异”应该是对称的但KL散度不是严格的“距离”度量。KL散度和ELBO的关系也值得记一下。变分自编码器VAE的核心推导就是最大化ELBO等价于最小化 KL(后验分布 || 先验分布) 加上一个重构误差。B站笔试如果出生成模型相关的题很可能绕不开这个概念。机器学习模型评估这块选择题喜欢考“过拟合和欠拟合的判断”“训练集、验证集、测试集的作用”“AUC的含义”等。对于B站这种内容平台算法工程师需要关心推荐模型在线上是否真的有效所以离线评估指标和在线AB实验的差异也是考点。3.3 粒子群、模拟退火、卡尔曼滤波优化与状态估计除了深度学习和经典统计机器学习B站这套笔试题还出现了一些更工程向的算法概念比如粒子群算法、模拟退火、卡尔曼滤波。看起来像“冷门知识点”其实和视频业务有直接关系。粒子群算法PSO的原理是模拟鸟群觅食。每个粒子代表解空间中的一个候选解它有自己的位置和速度。迭代时每个粒子会受两个因素影响一个是粒子自身历史最优位置pbest另一个是整个群体的最优位置gbest。速度更新公式是v w * v c1 * r1 * (pbest - x) c2 * r2 * (gbest - x)其中 w 是惯性权重c1 和 c2 是学习因子r1 和 r2 是随机数。粒子群算法本身是一种启发式全局优化方法不依赖梯度信息适合用在目标函数不光滑或者难以求导的优化问题上。模拟退火算法的核心是“以一定概率接受更差的解”。算法的起点是一个初始温度温度高的时候接受差解的概率大随着温度逐渐降低接受差解的概率变小最终收敛到较优解。这个“接受概率”通常写作 exp(-ΔE / T)其中 ΔE 是当前解和新解之间的目标函数差T 是当前温度。它和粒子群一样都是为了跳出局部最优。卡尔曼滤波则是状态估计领域的基础算法。它有两个核心步骤预测和更新。预测阶段用状态转移方程比如运动模型估计下一时刻的状态更新阶段把传感器观测值和预测值加权融合得到更精确的后验估计。这个过程有点像是“黄金分割”式的加权平均关键是卡尔曼增益K的计算。在B站卡尔曼滤波可以用于视频目标跟踪中的轨迹平滑。比如检测算法输出目标位置时会有抖动用卡尔曼滤波可以把轨迹变得更稳定。粒子群和模拟退火则可能出现在算法调参、工程优化等场景。笔试题一般只考“原理”和“适用场景”但如果你能结合业务举出一两个例子会显得更有优势。4. 编程题实战复盘4.1 字符串题最长无重复字符子串第二套编程题里有一道字符串处理题和“最长无重复字符的子串”非常接近。题目描述通常是给定一个字符串 s请找出其中不含重复字符的最长子串的长度。比如 s abcabcbb答案是3因为最长无重复子串是 abc。这道题的正解是滑动窗口。用两个指针 left 和 right 维护一个窗口right 不断向右移动同时用哈希表记录每个字符最后出现的位置。当遇到一个已经在窗口内出现过的字符时把 left 移到该字符上一次出现位置的下一个位置保证窗口内没有重复字符。int lengthOfLongestSubstring(string s) { unordered_mapchar, int lastIndex; int left 0, ans 0; for (int right 0; right s.size(); right) { char c s[right]; if (lastIndex.count(c) lastIndex[c] left) { left lastIndex[c] 1; } lastIndex[c] right; ans max(ans, right - left 1); } return ans; }这里最关键的一步是 left 的更新逻辑。不能简单地写成 left lastIndex[c] 1必须加一个条件 lastIndex[c] left。为什么因为哈希表里记录的这个字符上次出现位置可能已经不在当前窗口内了。如果上一次出现的下标比 left 还小说明那个重复字符早就被排除在窗口之外不需要移动 left。这个细节非常容易错我在面试别人时发现很多人栽在这里。时间复杂度是 O(n)空间复杂度是 O(min(n, 字符集大小))。如果字符串是纯ASCII字符集大小固定为128或者256空间可以认为是O(1)。4.2 动态规划题最长上升子序列另一道编程题常考的是最长上升子序列LIS。题目描述是给定一个无序整数数组找到其中最长严格上升子序列的长度。比如 nums [10, 9, 2, 5, 3, 7, 101, 18]答案是4最长上升子序列是 [2, 3, 7, 101] 或 [2, 5, 7, 101]。最直观的解法是动态规划O(n^2)。定义 dp[i] 表示以 nums[i] 结尾的最长上升子序列长度转移方程是dp[i] max(dp[j] 1)其中 0 j i 且 nums[j] nums[i]int lengthOfLIS(vectorint nums) { int n nums.size(); vectorint dp(n, 1); int ans 0; for (int i 0; i n; i) { for (int j 0; j i; j) { if (nums[j] nums[i]) { dp[i] max(dp[i], dp[j] 1); } } ans max(ans, dp[i]); } return ans; }O(n^2) 的解法虽然能过小数据但笔试里如果数组长度到10^5就会超时。更优的做法是“贪心 二分”维护一个 tails 数组其中 tails[i] 表示长度为 i1 的上升子序列的最小末尾元素。遍历每个元素 num在 tails 数组中二分查找第一个大于等于 num 的位置如果找到就替换如果找不到就追加到末尾。最终 tails 的长度就是最长上升子序列长度。int lengthOfLIS(vectorint nums) { vectorint tails; for (int num : nums) { auto it lower_bound(tails.begin(), tails.end(), num); if (it tails.end()) { tails.push_back(num); } else { *it num; } } return tails.size(); }这里的“替换”为什么有效因为对于同一个长度的上升子序列结尾元素越小后面就越容易接上更大的数。所以贪心地让 tails 里的每个元素尽量小是在为未来的扩展留空间。笔试中建议这样安排先写O(n^2)的DP并确保正确如果时间充裕或者知道更优解再改成O(nlogn)的版本。有时候面试官看的是“你能写出一个正确解法并且能分析它的复杂度”而不是盲目追求最优。4.3 图论题的通用解法第二套题不一定每场都考图论但Dijkstra、拓扑排序、并查集这些是算法岗的高频备选。如果遇到图论题先判断是什么类型单源最短路径、边权为正Dijkstra 优先队列。边权有负Bellman-Ford或SPFA。多源最短路径Floyd但要注意O(n^3)的复杂度限制。判断有向图是否有环拓扑排序用入度数组 队列。连通分量、动态连通性并查集。Dijkstra用优先队列优化后时间复杂度是 O((VE)logV)能高效处理稀疏图。我建议把模板背到能无脑写出来的程度因为考场上没有时间现场推导。拓扑排序适用于有依赖关系的场景比如B站的后台任务调度、课程学习顺序等。如果一个任务依赖另一个任务拓扑排序能给出合法的执行顺序。这一类题胜在代码模板固定多加练习就能快速写出。5. 场景与业务题算法岗不只是刷题5.1 图像和音频基础题B站作为视频平台算法岗笔试出现图像和音频相关的选择题非常正常。第二套题里就涉及了一些基础概念。Sobel算子是一种常见的边缘检测算子。它有两个卷积核一个检测水平方向变化一个检测垂直方向变化。用这两个核对图像做卷积得到的是梯度分量然后计算梯度幅值从而判断当前像素是否处于边缘位置。原理上它属于一阶微分算子对噪声比较敏感所以实际使用时常先做高斯模糊再算Sobel也就是常见的Canny边缘检测流程里的一个环节。音频重采样算法解决的是“采样率转换”问题。比如一段录音的采样率是44100Hz但项目需要把它转成16000Hz这时候就要做重采样。简单粗暴的做法是线性插值但会造成频谱混叠和高频失真更专业的做法是使用sinc插值或者带抗混叠滤波器后重采样。B站客户端播放器、语音审核、视频转码等场景都会涉及音频重采样。备这一类题不需要会手推公式但要知道“为什么需要重采样”“直接插值会有什么问题”“工程上怎么处理更稳”。这比死记一个算法的实现细节更符合B站笔试的调性。5.2 从BM25到推荐搜索场景题搜索相关度的计算里BM25是一个绕不开的排序公式。它综合考虑了词频TF、逆文档频率IDF和文档长度归一化。简单理解一个词在文档中出现得越多对相关度贡献越大但词越常见比如“的”“了”IDF权重越小避免常见词主导排序。BM25在小规模搜索场景下效果稳定而且可解释性强所以很多中小型系统都直接用BM25做召回或者粗排。B站笔试场景题如果问“如何设计搜索排序策略”可以这样回答召回阶段BM25 向量召回 热门兜底保证不遗漏。精排阶段用点击、播放时长、完播率等行为数据训练一个模型比如LR、GBDT或DNN。重排阶段做多样性打散、去重、内容质量过滤避免用户看到一个列表全是同一个作者或者同一类视频。这类题没有标准答案关键看逻辑是否完整能否从“用户需求”和“平台生态”两个角度同时思考。我当时准备场景题时把B站的核心场景列了一遍搜索、推荐、弹幕审核、视频去重、转码调度、内容理解。每个场景都整理了一套“问题定义-数据来源-算法选型-评估指标”的标准思路笔试时就算遇到没见过的题也能套用这个框架。6. 备考建议与避坑指南6.1 时间分配与做题顺序B站算法岗笔试题量说不上大但90分钟内要同时处理选择题和编程题时间并不宽裕。我的建议是选择题控制在35到40分钟内遇到不会的先标记不要死磕。有些选择题本身就是在干扰你投入太多时间反而影响后面的编程题。编程题先读三遍题想清楚输入输出格式再动手。很多错误不是算法本身而是没搞懂“读一行还是读多行”“输出是否需要换行”。先写暴力解法保住分数再用剩下的时间优化。比如LIS先写O(n^2)的DP如果时间充足再改二分版本。做题顺序上我习惯先做编程题再做选择题。原因是编程题分值占比大而且一旦进入状态思路流畅的时候写代码效率最高。选择题如果放在后面时间紧张蒙对的概率也比编程题高。6.2 我踩过的坑和常见失误说实话我当年复习算法岗笔试时踩过不少坑。这里整理了一些高频问题做一个速查表供大家考前扫一眼。问题原因解决办法KMP next数组算错题目定义的next数组版本和教材不一致先看题目给的是“最长相同前后缀”还是“失配跳转位置”堆排序稳定性记反只背结论不理解原理用三个元素的例子模拟一遍交换过程快速幂结果溢出中间结果没取模仔细检查 res 和 a 的每一步取模DP数组初始化错误忘记把最长子序列初始化为1明确dp[i]的含义再决定初值滑动窗口left更新错误没有判断重复字符是否在窗口内加条件 lastIndex[c] left在线笔试输入超时用了cin且没关同步使用 scanf/printf 或 ios::sync_with_stdio(false)时间分配失衡在一道选择题上纠结太久先标记最后有时间再回看在线笔试还有一个很隐蔽的问题代码编辑器通常没有编译提示手写完代码后最好能先在本地IDE验证一遍。如果平台支持“运行自测”一定要用给的样例测一下。有些同学喜欢在脑子里编译结果漏了分号或者写了中文括号白白丢分。另外在线笔试环境里输入输出格式的坑真的能让人崩溃。比如题目说“第一行一个整数T表示有T组测试数据”但你只处理了一组或者说“字符串可能包含空格”你用了cin s读到空格就断了。这些细节在本地可能不会暴露但在平台上就会导致0分。建议考前把各种输入方式练熟getline、cin、scanf、按行读取等。6.3 考前最后一周怎么准备最后一周不建议再啃难题偏题回归基础是最有效的。我自己的做法是每天手写一遍基础模板快速幂、LIS、Dijkstra、并查集、滑动窗口、二分查找。把排序算法的时间复杂度、稳定性表重新过一遍重点理解不稳定排序的原因。整理过往错题尤其是KMP next数组、DP初始化这类容易在细节上出错的题。刷一定量的选择题主要集中在机器学习基础、数据结构、算法特性判断上保持手感和反应速度。把B站笔试常见的业务场景搜索、推荐、内容理解梳理成自己的话术框架。这里特别想强调“手写模板”这件事。手写并不是让你背代码而是让你在写的过程中重新理解每一步的逻辑。比如快速幂那两行 res res * a % p 和 a a * a % p如果你能一边写一边说出为什么这么做考场上就不会卡壳。另外考前可以专门训练一下“看题审题”的能力。拿到一道编程题先不要急着敲代码把题目里的关键词圈出来输入范围、是否多组输入、是否要求严格递增、是否允许重复字符。这些关键词直接决定了算法选择和边界处理。我吃过一次亏题目写的是“非严格递增”我没注意用了严格递增的判断结果样例都过了但提交全错。最后说点题外话这套题我现在回头看最大的价值不是题目本身而是它传递出的一个信号B站算法岗真正看重的是“基础是否扎实能不能在限定时间内稳定输出”而不是会不会背冷门公式。KMP、堆排序、快速幂、LIS、K-means、KL散度这些东西在真实工作中可能不会每天都直接用但它们构成了一个工程师判断问题、拆解问题、优化方案的底层能力。我当时备考时最大的体会是不要瞧不起“简单题”更不要在“难题”上自我感动。把KMP的next数组推导熟练到30秒内能算完把快排和堆排序的代码写到肌肉记忆把滑动窗口和LIS的边界条件刻进脑子里上考场的时候心里就有底了。与其焦虑笔试会不会出偏题不如把基础题做到百分百稳。这一套配方对B站适用对其他大厂算法岗同样适用。
返回列表