ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

2019B站算法岗笔试复盘:KMP、粒子群与动态规划全解析

2019B站算法岗笔试复盘:KMP、粒子群与动态规划全解析 1. 试卷整体风格与考察逻辑1.1 题型分布和分值构成2019年B站秋招技术岗算法方向这套笔试题总体来说走的是“稳中有变”的路线。和当年其他互联网大厂的笔试相比它并没有刻意堆砌偏题怪题而是在数据结构、经典算法、机器学习基础这几个大方向上做了一次比较全面的摸底。整套试卷大致由三部分构成单选题、多选题和编程题其中选择题覆盖的知识面比较广从C语言基础到概率统计都有涉及编程题则集中在字符串处理、动态规划和贪心算法这几个高频考点上。先说选择题。这部分大概有20道左右分值占比接近40%单题分值不高但架不住数量多。考察的内容非常典型排序算法的时间复杂度比较、KMP算法中next数组的计算、指针和数组的关系、常见机器学习模型的基本原理等等。说实话这些题目单独拎出来任何一道都不算难但放在一起考察的是你计算机基础是否扎实、有没有形成系统性的知识网络。很多同学平时刷题习惯了LeetCode那种“给一个明确问题、写一个完整解法”的模式反而在这种碎片化的知识点考察上容易翻车。编程题部分一共3道分值占比超过一半。从题目难度梯度来看B站显然是想通过这三道题筛选出“基础扎实、思路清晰、代码能力过硬”的候选人。第一道题通常是热身的字符串处理中等偏易第二道题开始上强度会考到动态规划或者稍微复杂一点的贪心第三道题则是拉开差距的关键往往需要结合多种技巧或者对问题有比较深的理解才能拿到高分。我当年做完这套题的感觉是时间不算特别充裕选择题需要快速判断编程题必须给足时间写完整、跑通。1.2 难度梯度和选题意图分析B站这套笔试题的选题意图可以看到几个明显的倾向。第一非常重视基础数据结构的熟练度。数组、链表、栈、队列、树、图这些基础数据结构几乎都有涉及但不会直接问你“栈的特点是什么”这种死记硬背的问题而是会把数据结构放到具体的算法场景里考察。比如给你一个场景让你判断哪种数据结构最合适或者给一段代码让你分析时间复杂度和空间复杂度。这说明B站要的不是背概念的选手而是真正理解数据结构本质、能够在工程实践中选对数据结构的人。第二算法考察集中在经典算法及其变体。KMP、排序、二分查找、动态规划、贪心、图的最短路径这些都是算法岗笔试的常客。尤其是KMP的next数组几乎是每年必考。为什么因为KMP很好地考察了一个人对字符串匹配问题的理解深度——你不仅要会用暴力解法还要理解如何通过预处理避免回溯这种“空间换时间”的思路在工程中到处都是。B站作为一家视频平台弹幕过滤、内容审核、敏感词匹配这些场景都离不开字符串匹配算法所以考KMP可以说是“源于业务、高于业务”。第三机器学习基础占比不低。作为算法岗的笔试机器学习基础知识是绕不开的。从热词里可以看到“粒子群算法原理”、“机器学习算法”、“深度学习算法”这些关键词说明当年这套题里确实有相当比例的题目在考察AI相关的理论知识。这部分考察的深度不会特别深但覆盖面很广从传统机器学习到深度学习都有涉及需要你对常见的模型原理、损失函数、优化方法有一个清晰的认识。整体来看这套题的难度在当年属于中等偏上。相比那些动辄“压轴题直接上硬核数学推导”的笔试B站算是比较友好的。但“友好”不等于“容易”它对基础知识的考察非常细致任何一个知识点掌握得不牢固都可能在选择题里丢分。2. 核心笔试题目复盘2.1 KMP的next数组看似简单但极容易错KMP算法那道题原题大概是这样的对于模式串 p abacaba求其 next 数组。这里有个关键信息题目中注明了 next[i] 的定义方式。很多人在这一步就被绕晕了——因为KMP的next数组在不同教材、不同参考书里的定义是存在差异的。常见的定义有两种。一种是 next[i] 表示模式串前 i 个字符组成的子串中最长相等前后缀的长度。注意这里的“前缀”不包含整个子串本身“后缀”同理。按照这个定义对于 abacabanext[1] 0第1个字符a没有真前缀和真后缀长度为0next[2] 0前两个字符ab前缀a和后缀b不相等长度为0next[3] 1前三个字符aba前缀a和后缀a相等长度为1next[4] 0前四个字符abac没有相等的前后缀长度为0next[5] 1前五个字符abaca前缀a和后缀a相等长度为1next[6] 2前六个字符abacab前缀ab和后缀ab相等长度为2next[7] 3整个串abacaba前缀aba和后缀aba相等长度为3另一种定义则从0开始计数next[0] -1然后 next[i] 表示前 i 个字符的最长相等前后缀长度。两种定义下数组的值会有区别考察的是你能否根据题目给出的定义准确计算。这道题的真正考点不在于计算本身而在于你是否理解 next 数组到底在做什么。KMP 算法的核心思想是当匹配失败时不需要把模式串从头开始而是根据 next 数组跳到已经匹配过的位置继续比较。这个“跳”的过程本质上是利用了模式串自身的重复结构。面试的时候面试官很可能会追问为什么KMP能做到 O(mn) 的时间复杂度暴力匹配的时间开销主要在哪里这时候你要答出——暴力匹配在失配时需要回溯主串指针而KMP通过预处理next数组让主串指针从不回溯模式串指针根据next数组调整位置从而把时间复杂度从 O(m*n) 降到 O(mn)。我在实际刷题过程中发现很多同学能背下KMP的模板代码但一旦题目换个定义方式、或者考一个具体的字符串计算就容易出错。建议备考时不要只背代码而是自己动手推导一遍next数组的生成过程理解每一步的跳转逻辑。这个基本功打牢了不管题目怎么换皮都能应对。2.2 粒子群算法原理一道考察知识面的送分题热词里出现了“粒子群算法原理”这说明当年这套题里应该有一道关于粒子群算法PSO的题目。粒子群算法是一种群体智能优化算法灵感来源于鸟群觅食行为。在算法岗笔试中出现这道题考察的不是你能不能实现PSO而是你是否了解其基本流程和核心公式。粒子群算法的基础流程并不复杂。首先初始化一群粒子每个粒子代表问题解空间中的一个候选解有自己的位置和速度。然后通过迭代更新每个粒子的位置和速度更新公式包含三个部分惯性项粒子保持上一时刻运动趋势的能力由惯性权重 w 控制个体认知项粒子向自身历史最优位置pbest靠近的趋势由学习因子 c1 控制群体认知项粒子向整个群体历史最优位置gbest靠近的趋势由学习因子 c2 控制速度更新公式可以写为v[i] w * v[i] c1 * r1 * (pbest[i] - x[i]) c2 * r2 * (gbest[i] - x[i])其中 r1 和 r2 是 [0, 1] 之间的随机数。位置更新公式就是 x[i] x[i] v[i]。看到这个公式你应该能感受到粒子群算法的核心思想每个粒子在解空间中飞行既保留自己的运动惯性又参考自身经验和群体经验来调整方向。这种“个体经验 群体协作”的机制使得整个群体能够在解空间中搜索到较好的区域。笔试可能会考的点包括PSO是全局优化算法还是局部优化算法答案是没有绝对的好坏它通过调整惯性权重来实现全局搜索和局部搜索的平衡。w 大时全局搜索能力强w 小时局部搜索能力强。还可能考PSO和遗传算法的区别——PSO不需要编码解码操作参数更少实现更简单但容易陷入局部最优遗传算法有选择、交叉、变异操作全局搜索能力相对更强。这类题目属于“知道就会不知道就蒙”的类型。备考时不需要对每种优化算法都深入到底层数学推导但要对常见算法的核心思想、适用场景、优缺点有一个基本认知。B站作为内容平台推荐系统、内容分发策略里都可能用到各种优化算法所以考察这部分知识是很务实的。2.3 排序算法全家桶基础题也要答出亮点排序算法是算法岗笔试永远绕不开的话题。这套题里关于排序的选择题至少有两三道考察的内容包括各排序算法的时间复杂度对比、稳定性分析、以及特定场景下应该选择哪种排序算法。我把常见的排序算法做了个对比表方便备考时快速查阅排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性冒泡排序O(n²)O(n²)O(1)稳定选择排序O(n²)O(n²)O(1)不稳定插入排序O(n²)O(n²)O(1)稳定希尔排序O(n^1.3)O(n²)O(1)不稳定归并排序O(n log n)O(n log n)O(n)稳定快速排序O(n log n)O(n²)O(log n)不稳定堆排序O(n log n)O(n log n)O(1)不稳定计数排序O(n k)O(n k)O(k)稳定桶排序O(n)O(n²)O(n k)稳定基数排序O(nk)O(nk)O(n k)稳定这个表格基本上就是排序算法的“身份证”备考时一定要烂熟于心。但光是记住表格还不够笔试题目往往会有一些变体。比如问你对一个近乎有序的数组排序用什么算法最合适答案应该是插入排序因为插入排序在数据基本有序的情况下时间复杂度趋近于 O(n)而快速排序在这种情况下反而容易退化到 O(n²)。再比如数据量很大、内存放不下需要对磁盘上的数据进行外部排序用什么算法这时候归并排序是最合适的因为它天然适合分治处理能够通过多路归并实现外部排序。还有一个容易踩坑的点是稳定性。所谓稳定性指的是相等的元素在排序后是否保持原有的相对顺序。面试时你不仅要能说出哪些排序稳定哪些不稳定还要能解释原因。比如快速排序为什么不稳定因为它的分区操作中pivot的移动可能改变相等元素的相对位置。堆排序为什么不稳定因为堆调整过程中父子节点的交换会打乱相等元素的顺序。排序算法这部分我建议备考时做到两个层面第一能用白板写出至少三种排序算法的完整代码第二能够分析任意一种排序算法在特定场景下的表现以及为什么选择它而不选择其他算法。这样不管是笔试还是面试都能稳操胜券。3. 编程题实战拆解3.1 字符串处理题最长回文子串B站算法笔试的编程题第一题往往是一道字符串处理的题目。这里我拿一道非常经典的题目来做拆解给定一个字符串 s找出 s 中最长的回文子串。这道题至少有三种解法暴力枚举、动态规划、中心扩展。暴力枚举的复杂度是 O(n³)显然不适合笔试场景动态规划可以将复杂度降到 O(n²)而中心扩展方法同样能做到 O(n²)但常数项更小代码也更简洁。动态规划的思路是这样的定义一个二维数组 dp[i][j] 表示 s[i..j] 是否是回文串。状态转移方程为dp[i][j] (s[i] s[j]) and (j - i 3 or dp[i 1][j - 1])其中 j - i 3 处理的是长度为 1 或 2 的子串——长度为1的子串一定是回文串长度为2的子串只要两个字符相等就是回文串。这个状态转移方程的含义很直观一个子串是回文串当且仅当首尾字符相等并且去掉首尾后的子串也是回文串。中心扩展法的思路则更加巧妙遍历每一个可能的回文中心然后向两边扩展。回文中心有两种情况——奇数长度回文的中心是一个字符偶数长度回文的中心是两个字符之间的空隙。所以对于长度为 n 的字符串一共有 2n - 1 个可能的中心。对每个中心向两边扩展记录最长的回文子串。下面是我用C写的中心扩展法参考实现class Solution { public: string longestPalindrome(string s) { if (s.empty()) return ; int start 0, maxLen 1; for (int i 0; i s.size(); i) { // 奇数长度回文中心为 i expandFromCenter(s, i, i, start, maxLen); // 偶数长度回文中心在 i 和 i1 之间 expandFromCenter(s, i, i 1, start, maxLen); } return s.substr(start, maxLen); } private: void expandFromCenter(const string s, int left, int right, int start, int maxLen) { while (left 0 right s.size() s[left] s[right]) { left--; right; } int curLen right - left - 1; if (curLen maxLen) { maxLen curLen; start left 1; } } };笔试的时候这道题的关键在于你能不能快速选择正确的解法。我看到不少同学一上来就写暴力枚举三重循环嵌套结果跑大数据直接超时。所以做题之前先想清楚时间复杂度的上限再动手写代码这个习惯非常重要。另外注意边界条件的处理。空字符串、单字符字符串、全相同字符的字符串这些都是面试官喜欢出的测试用例。写代码时一定要把边界情况考虑清楚避免出现数组越界或者死循环。3.2 动态规划题编辑距离编辑距离是一道非常经典的动态规划题目也是B站笔试编程题中很有可能会出现的一道题。题目描述是给定两个单词 word1 和 word2计算出将 word1 转换成 word2 所使用的最少操作数。可以对一个单词进行三种操作插入一个字符、删除一个字符、替换一个字符。这道题的动态规划状态设计非常经典。定义 dp[i][j] 表示 word1 的前 i 个字符转换成 word2 的前 j 个字符所需要的最少操作数。状态转移方程如下如果 word1[i-1] word2[j-1]dp[i][j] dp[i-1][j-1]因为当前字符已经匹配不需要额外操作如果不等dp[i][j] min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) 1其中 dp[i-1][j] 对应删除操作删除 word1 的第 i 个字符dp[i][j-1] 对应插入操作在 word1 中插入 word2 的第 j 个字符dp[i-1][j-1] 对应替换操作将 word1 的第 i 个字符替换为 word2 的第 j 个字符。初始化条件是 dp[0][j] j从空字符串变成 word2 的前 j 个字符需要插入 j 次dp[i][0] i从 word1 的前 i 个字符变成空字符串需要删除 i 次。我写一个Python版本的参考实现因为这个题目用Python写起来更加简洁明了def minDistance(word1: str, word2: str) - int: m, n len(word1), len(word2) dp [[0] * (n 1) for _ in range(m 1)] for i in range(m 1): dp[i][0] i for j in range(n 1): dp[0][j] j for i in range(1, m 1): for j in range(1, n 1): if word1[i - 1] word2[j - 1]: dp[i][j] dp[i - 1][j - 1] else: dp[i][j] min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) 1 return dp[m][n]编辑距离这道题考察的核心是你能不能正确理解动态规划的状态定义和状态转移。很多人一看到“最少操作数”就想到BFS或贪心但实际上这道题的贪心解法是不成立的——你可能需要通过多次操作才能达到最优解局部最优并不能推导出全局最优。所以看到这种“最少/最多/最短”的问题优先考虑动态规划是一个比较稳妥的思路。笔试中还有一个常见的追问如果要求输出具体的编辑过程怎么办这需要在状态转移时记录每一步的选择路径然后反向回溯得到完整的操作序列。这个扩展问题在面试中出现的概率很高建议备考时一并掌握。3.3 贪心与场景题视频清晰度切换问题B站作为视频平台编程题往往会结合自身的业务场景来出题。有一类很典型的题目是给定用户观看视频时的清晰度切换记录要求计算某种最优策略。这种题目的本质是贪心算法但是放在业务场景里会增加一些理解成本。举个例子假设一个视频有多个清晰度档位用户在观看过程中可以切换清晰度。给定一个数组 aa[i] 表示第 i 个时间片段用户观看的清晰度档位。现在规定如果相邻两个时间片段的清晰度不同就会产生一次卡顿风险。你可以在某些时间片段强制用户观看更高或更低的清晰度但强制切换本身会产生成本。问如何以最少的总成本消除所有卡顿风险这道题的核心逻辑是贪心当检测到相邻片段清晰度不同时你有两种选择——要么调整前一个片段要么调整后一个片段选择成本更小的方案即可。时间复杂度 O(n)空间复杂度 O(1)。def minCostToSmooth(a, cost_change): n len(a) total 0 for i in range(1, n): if a[i] ! a[i - 1]: total min(cost_change, abs(a[i] - a[i - 1])) return total这种题目设计的目的是考察候选人能否把业务问题抽象成算法问题。在真实的推荐系统、视频分发系统中类似的资源分配、策略优化问题非常多所以B站出这类题并不让人意外。做这种业务场景题时一个重要的技巧是先读清楚题目明确输入输出的定义再考虑用哪种算法。不要被场景描述干扰直接把核心问题提取出来。如果能做到这一步即使是没见过的题目也能在短时间内找到解题方向。4. 高频失分点与避坑指南4.1 手撕代码时的细节规范笔试编程题最可惜的丢分点不是不会做而是细节处理不到位。我整理了几个高频失分点这些都是在实际笔试中反复出现的问题。第一个是数组越界。很多同学在写循环时没有注意边界条件导致数组越界访问。比如遍历字符串时用 s[i] 访问但没有检查 i 是否小于字符串长度。这类错误在本地调试时可能不明显但笔试系统的测试数据覆盖很广一旦越界就是运行时错误直接零分。第二个是整数溢出。当计算数值比较大的情况下int 类型的范围可能不够用。比如计算最长回文子串的起始位置时如果字符串长度非常大用 int 存储索引可能溢出。解决方案是使用 long long 类型或者在设计算法时避免出现大数运算。第三个是特殊输入的处理。空数组、空字符串、只有一个元素的数组、全相同元素的数组——这些边界情况一定要单独考虑。很多算法在处理这些输入时会直接崩溃或者返回错误结果。第四个是代码风格问题。虽然笔试主要看结果但清晰的代码风格能帮助你在代码评审环节获得好感。变量命名要有意义不要用 a、b、c 这种无意义的名字关键逻辑要写注释代码要保证缩进一致。这不仅是给面试官看的也是给你自己看的——清晰的代码更容易调试。4.2 时间复杂度隐性考核笔试题中有些题目乍一看没有明确要求复杂度但通过数据范围可以推断出来。比如题目说输入数组长度最大为 10^5那你写 O(n²) 的算法大概率会超时必须想办法优化到 O(n log n) 或 O(n)。举一个具体的例子给定两个数组求它们的交集。如果你用暴力枚举两个 for 循环嵌套时间复杂度是 O(n*m)。当数组长度都是 10^5 时这个复杂度是 10^10 次操作显然无法在1秒内完成。正确的做法是用哈希集合存储一个数组的元素然后遍历另一个数组判断是否在集合中时间复杂度降到 O(nm)。所以拿到题目后第一件事不是写代码而是看数据范围估算出你能接受的算法复杂度然后再动手。这是一个成熟的算法工程师的基本素养也是笔试考察的重要能力。我在平时刷题时会刻意训练这个习惯每道题做完了会复盘一下自己的解法时间复杂度是多少有没有更优的方案如果数据范围扩大10倍还能不能跑通。时间长了对复杂度的敏感度就会大幅提升。4.3 选择题的排除法与快速判断B站这套笔试题的选择题有很多是不需要精确计算就能通过排除法选出答案的。比如排序算法稳定性这道题题目问下面哪些排序算法是稳定的即使你不记得全部答案只要知道快速排序和堆排序不稳定就能排除掉含有这两个选项的选项。再比如时间复杂度比较问你快速排序在什么情况下时间复杂度退化到 O(n²)答案是当数组已经有序或基本有序时。这个知识点其实是快排的经典缺陷——如果每次分区都选到最大或最小的元素作为pivot分区就不均匀递归深度从 log n 退化到 n。做选择题时还有一个小技巧注意题目中的关键词。“正确的是”、“错误的是”、“包含”、“不包括”这些词决定了你的选择方向。我在实际考试中见过不少同学因为看错题目要求把“错误的是”看成了“正确的是”白白丢分。这种失误非常可惜一定要在读题时把关键词圈出来。对于多选题我的建议是“宁缺毋滥”。如果某个选项拿不准不要选——多选的评分规则通常是漏选得部分分、错选得零分所以宁可少选也不要去赌一个不确定的选项。5. 关于B站算法岗笔试的一些个人体会这套2019年的笔试题放到现在来看很多题目依然有很强的参考价值。我在备考期间反复刷了几遍每次都会有一些新的收获。有一个比较深的体会是B站的题目风格整体上非常务实不追求“难倒你”而是追求“看清你”——看清你的基础是否扎实、思维是否敏捷、代码是否干净。对于准备算法岗笔试的同学我有几个比较实操的建议。第一一定要重视基础数据结构和经典算法尤其是排序、KMP、动态规划这三块。这些都是高频考点考到的概率非常大。不要觉得排序很简单就不复习真的让你手写一个快速排序很多人在10分钟内是写不出完全正确的代码的。第二练习白板编程。笔试和平时在IDE里刷题有很大区别没有自动补全、没有编译提示、甚至没有调试器。你要习惯在没有辅助工具的情况下写出正确的代码。我的训练方法是刷题时直接打开一个空白文档不看参考代码从零开始写写完再编译运行验证。第三做题时注意时间分配。选择题不要纠结太长时间一道题如果超过2分钟还没有思路先标记跳过最后再回来处理。编程题一定要留足时间宁可选择题少对几道也要保证编程题能完整提交。第四多看看目标公司的业务场景。B站是视频平台所以和视频推荐、弹幕分析、内容审核相关的算法知识点都有可能出现在笔试题目里。平时可以多关注这些方向的技术博客和论文积累一些领域知识遇到业务场景题时会有更大把握。最后想说的是笔试题只是求职路上的第一道关卡它考察的是你的基础能力和思维习惯并不完全等同于你的真实工作能力。但反过来看如果连笔试都过不了也很难有后续展示自己实力的机会。所以踏踏实实把基础打牢认真对待每一次笔试和复盘这个过程本身就会让你成长很多。这套题我反复刷了好几遍每次都有新收获也推荐你把里面的每道题都吃透。如果你正在准备算法岗的面试希望你也能从这套题里找到自己的薄弱环节针对性地补齐。祝大家都能拿到心仪的offer。
返回列表