十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

映客算法B卷全拆解:从KMP到滑动窗口的笔试备考指南

映客算法B卷全拆解:从KMP到滑动窗口的笔试备考指南 去年春招季映客的算法B卷在不少求职群里被聊得很热。我不是当年考这套题的人但近两年陆续有不少学弟学妹拿着回忆版题目来找我对答案、聊思路一来二去这套卷子的考法和风格我已经摸得比较清楚了。这篇文章不做“真题搬运”而是从命题角度帮你拆一下这套B卷到底在考什么每一类题背后想筛的是什么能力以及备考的时候怎么练才最有效。适合正在准备算法岗笔试、尤其是想冲直播/音视频类互联网公司算法岗位的同学。标题里那个“算法”其实是很多人容易忽视的关键词。同样是算法工程师做推荐的和做音视频的笔试重点差别很大。映客的业务以直播为主弹幕、礼物、推荐、内容审核这些都是算法落地的方向所以B卷既考经典数据结构和算法也会带一点工程思维的味道而不是单纯的LeetCode刷题机器。这篇文章我会从题型结构、核心考点、典型案例、答题策略四个维度来讲尽量还原一套完整的算法笔试题应该怎么看、怎么准备。1. 整张B卷的设计逻辑不是难是覆盖面广1.1 常见的题型结构笔试时间一般90到120分钟题目量大概在20到30题之间。从往年各类算法B卷的分布来看通常会分成三个部分选择题考察基础概念和原理比如时间复杂度计算、排序算法稳定性、数据结构特性偶尔会有几道机器学习基础题比如过拟合的解决方案、常见损失函数的适用场景。简答/填空题可能会让你写出某算法的时间复杂度或者补全KMP算法的next数组有时候会让你手写一个简单的动态规划转移方程。编程题一般2到3道是整张卷子的重头戏分值占比最高通常覆盖字符串处理、动态规划、图论或贪心这几类经典题型。映客这套B卷从出题风格来看选择题里会刻意埋一些“看起来简单但容易错”的细节编程题则更偏重“在约束条件下找最优解”的能力而不会出那种需要背模板才能做出来的偏题怪题。我记得有同学反馈题量并不大但每道题的陷阱密度挺高时间压力主要来自于反复推敲和边界条件的处理。1.2 考点分布背后的出题意图为什么要这样设计说到底笔试是人才筛选的漏斗第一层它不需要考出最牛的解题高手而是要快速把那些“基础不扎实、代码能力不过关、算法思维没建立”的候选人筛掉。所以你看选择题考的是概念的准确度能不能一眼看出某个排序算法在近乎有序数组里反而退化成O(n²)这反映的是你平时有没有真正理解算法的适用场景而不是死记硬背排序模板。编程题考的则是三个维度的综合能力能不能读明白题意、能不能设计出合理的数据结构、能不能在限定时间内写出无bug的代码。这里我想多说一句很多刷题党容易忽略“工程思维”的考察。同样是求最长公共子序列面试官想知道的不只是你会不会背转移方程而是你对空间复杂度有没有敏感度能不能从O(m*n)优化到O(n)。这种优化意识恰恰是实际工作中改代码、做性能优化时最常用的能力。1.3 基于岗位特点的题型漂移有一点需要注意算法岗并不是只有“算法工程师”这一个方向。映客这类直播平台算法方向可能覆盖推荐系统、内容理解、音视频处理、风控反作弊等。不同方向对应的笔试侧重点会有一点漂移。推荐/搜索方向会多考一些机器学习基础比如特征工程思维、常见排序指标的对比以及用户行为序列的建模思路编程题里图论和动态规划的权重会更高。音视频/图像方向会涉及一些信号处理或图像处理的基础概念比如采样率、卷积操作、滤波器的概念但笔试阶段一般不会让你硬算主要还是看编程基本功。风控方向会更偏爱对异常检测、聚类算法、分类模型的理解代码题可能是一场模拟题考察你设计规则和异常处理的能力。所以你在准备的时候最好先去了解目标岗位的业务方向再判断复习重点。如果准备的是通用算法岗建议还是按“数据结构 经典算法 基础机器学习”这个铁三角来打底这套B卷的考察范围也基本围绕这三大块展开。2. 核心细节拆解最容易丢分的三类题2.1 字符串处理KMP这类基础题要懂为什么要这样跳转字符串题在几乎所有算法笔试里都会出现映客这套B卷也不例外。原因很简单直播平台的弹幕过滤、文本审核、关键词命中底层全是字符串匹配和处理。B卷里如果出现一个模式串让你求next数组或者让你实现一个字符串匹配的代码一点都不意外。这里就要说到热搜里反复出现的KMP算法了。很多人刷KMP的时候是背代码过的背得滚瓜烂熟但一到笔试变个考法就懵了。其实KMP的核心就一句话当匹配失败时利用已经匹配的部分让模式串尽可能多地跳过一些位置而不是从头再匹配。举个例子模式串 p abacaba让你求它的next数组。如果只是背代码你可能会套模板写出结果但你未必理解为什么某个位置j的next值是2。我建议你用这个思路去推导next[i]表示的是“模式串前i个字符组成的子串中最长的相同前缀后缀的长度”。这里的“相同前缀后缀”指的是这个子串的最长真前缀和真后缀相等并且不重合。手算的时候可以这样i 1子串 a前缀后缀都为空next[1] 0i 2子串 ab前缀a、后缀b不相等next[2] 0i 3子串 aba前缀 a、后缀 a相等且长度为1前缀 ab 和后缀 ba 不相等所以 next[3] 1i 4子串 abac前缀 a 和后缀 c 不等前缀 ab 和后缀 ac 不等前缀 aba 和后缀 bac 不等next[4] 0i 5子串 abaca前缀 a 和后缀 a 相等长度为1再看ab和ca不等aba和aca不等abac和baca不等所以 next[5] 1i 6子串 abacab前缀 ab 和后缀 ab 相等长度为2前缀 a 和 b 不等所以 next[6] 2i 7子串 abacaba前缀 aba 和后缀 aba 相等长度为3所以 next[7] 3这个推导过程看着简单但它能帮你建立对KMP的直觉。笔试里如果考到KMP很可能不会直接让你写完整代码而是让你填下一跳的位置或者给一个匹配失败的场景问你模式串应该往后移多少位。把next数组的语义搞清楚这类题无论怎么变你都能应对。2.2 动态规划状态定义比转移方程更重要动态规划是算法笔试的“必考大户”映客的B卷也不例外。但很多人对动态规划的理解停留在“套模板”一看到题目就问这是背包吗这是LIS吗这是区间DP吗这种思路最大的问题是一旦题目换了一个复杂的场景包装你就没法识别出它背后的DP结构。我建议你把注意力放在两件事上状态定义和边界条件。状态定义其实是整个DP题最难的部分。一个常见的思路是“最后一步分析法”假设你已经求出了所有子问题的答案那么最后一步应该做什么这个操作之前的子问题是什么举个例子最长上升子序列以第i个元素结尾的最长上升子序列长度这个状态定义就抓住了“最后一步”的确定性——上升子序列必须有一个结尾元素而结尾元素一定是原序列中的某个元素。转移方程有时候反而是最简单的部分。难的是边界条件比如数组长度为0、dp数组初始值应该是0还是1、索引从0开始还是从1开始这些细节最容易在笔试中造成低级错误。另外很多B卷的DP题会隐含一个“空间优化”的考察点。比如一个二维DP状态转移只依赖上一行的数据那你就应该想到用滚动数组把空间复杂度从O(m*n)降到O(n)。这类优化在笔试中可能不会直接要求但在代码里体现了你的思维深度是一个加分项。2.3 排序与贪心基础不牢地动山摇排序算法在选择题里出现频率极高而且经常是以“反常识”的方式考。比如快速排序在数组完全有序的情况下复杂度退化到O(n²)这个大家都知道但换个问法——哪种排序算法在数据基本有序时性能最差很多人就容易在快速排序和堆排序之间犹豫。这里需要有一个清晰的认识排序算法没有绝对的优劣只有适用场景的区别。排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性适用场景冒泡排序O(n²)O(n²)O(1)稳定几乎不用仅教学快速排序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(nk)O(nk)O(k)稳定整数范围远小于n时贪心算法也是笔试中不容易答好的点。很多人觉得贪心就是“每步选最优”但真正的难点在于证明贪心策略的正确性。笔试不要求你写严格证明但你至少要能通过反例来判断这个题能不能用贪心。常见的陷阱是局部最优不一定导致全局最优比如背包问题里按价值密度从大到小贪心选但0-1背包问题用这种方法是不对的。所以在做贪心题的时候我习惯先试着构造一个反例如果构造不出来再用贪心去解同时心里清楚这个解法的正确性逻辑是什么。这样做题的正确率会高很多。3. 一道典型编程题的完整推演接下来挑选一道B卷里很可能出现的编程题做成完整的推导过程。这道题不是我凭空乱编而是贴合直播场景里“弹幕去重”和“最长有效内容提取”的真实需求很多公司的笔试都喜欢把这类业务背景直接包装进算法题。它本质上是一道经典的滑动窗口题。3.1 题目背景假设你收到一条弹幕它是一个字符串s里面可能包含各种字符包括emoji和特殊符号。你需要计算出这个字符串中“不含重复字符的最长子串”的长度并输出这个子串。例如输入 s abcabcbb最长不含重复字符的子串是 abc长度为3。输入 s bbbb最长不含重复字符的子串是 b长度为1。输入 s pwwkew最长不含重复字符的子串是 wke长度为3注意pwke是一个子序列不是子串。3.2 暴力解法的思路与问题很多人的第一反应是枚举所有子串然后判断每个子串有没有重复字符。三层循环外层枚举起点中层枚举终点内层检查重复。时间复杂度O(n³)这在大数据量下是绝对不能接受的。假设字符串长度在10^4量级O(n³)就意味着10^12次操作即使每次操作只需要一纳秒也要1000秒笔试环境里肯定超时。所以这道题考察的核心就是你会不会用滑动窗口把时间压到O(n)。3.3 滑动窗口 哈希表滑动窗口的思路很直观我们维护一个窗口窗口左边界是left右边界是right窗口内的所有字符都不重复。右边界不断向右扩张每次扩张时判断当前字符是否已经在窗口内出现过。如果出现过就把左边界移动到“上一次出现该字符的位置后面一位”然后继续扩张。这里需要用到一个哈希表或者数组来记录每个字符最近一次出现的下标。在遍历字符串的过程中我们不断更新最长窗口的长度。这个算法的本质是“一次遍历、两指针移动”每个字符最多被访问两次所以时间复杂度是O(n)空间复杂度是O(字符集大小)常见实现下是O(min(n, 字符集大小))。3.4 代码实现这里给出C的实现方便性能和可读性兼顾#include iostream #include string #include vector #include algorithm using namespace std; int lengthOfLongestSubstring(string s) { int n s.size(); int left 0; int maxLen 0; // 字符集假设是ASCII 128也可以用 unordered_map vectorint lastIndex(128, -1); string longestSub ; for (int right 0; right n; right) { char c s[right]; // 如果当前字符在窗口内出现过更新左边界 if (lastIndex[c] left) { left lastIndex[c] 1; } // 记录当前字符的最新出现位置 lastIndex[c] right; // 更新最长长度和子串 int curLen right - left 1; if (curLen maxLen) { maxLen curLen; longestSub s.substr(left, curLen); } } return maxLen; } int main() { string s abcabcbb; cout lengthOfLongestSubstring(s) endl; // 输出 3 cout longestSub endl; // 输出 abc return 0; }注意一个细节if (lastIndex[c] left)这个判断非常重要不能只判断lastIndex[c] ! -1否则左边界已经滑过某个字符之后该字符的下标仍然存在会导致错误更新。这是这道题最容易写错的地方。3.5 复杂度与进一步优化时间复杂度O(n)空间复杂度O(字符集大小)。如果字符包含Unicode就不能用固定数组可以改用unordered_mapchar, int。进一步优化点如果要求输出最长子串本身把代码里的substr改成记录左边界和长度最后再取子串可以避免频繁调用substr带来的额外开销。这道题虽然简单但包含了很多笔试答题的关键素质你能不能在有限时间内读懂题意能不能选对数据结构能不能把边界条件考虑完整。这些素质B卷的编程题就是在反复考察。4. 笔试现场的常见坑点与提效技巧4.1 输入读取与边界最冤的失分点笔试平台五花八门有的用牛客有的用赛码有的用自己的OJ。输入输出的格式要求各不相同尤其是字符串读取有时候一行有空格有时候用逗号分隔有时候是到EOF为止。很多同学算法想得出来结果卡在输入解析上白白丢分。我的建议是提前熟悉你目标公司用的笔试平台。去牛客上找到对应的题库或模拟题先练习几道输入输出题把getline、cin和scanf的差异摸清楚。如果是C注意带空格的字符串要用getline(cin, s)如果是Python注意input().strip()和split()的边界情况。另外笔试循环输入的时候注意每一轮结束要不要清空全局变量或者容器。我见过太多人第一轮测例通过了第二轮因为全局变量没有清空而答案错乱这种低级失误最可惜。4.2 时间复杂度的预判写完会不会超时笔试和IDE里刷题不同你看不到实时评测响应吗有的平台能看到有的看不到。这就更要在写代码之前对数据规模做出判断。一个直接的经验法则1秒的时限内普通代码大概能跑10^7到10^8次基本操作。如果n ≤ 10^3O(n²) 可以接受。如果n ≤ 10^5O(n log n) 是最稳妥的。如果n ≤ 10^7基本只能考虑O(n)或者O(n log n)但常数特别小的算法。我见过不少同学在数据范围明确写着n 10^5的情况下还去写两层循环这显然没有经过大脑。拿到题目第一步先看约束再定算法这是职业习惯也是应试习惯。4.3 调试技巧不打印也能定位问题笔试现场调试时间有限很多时候你没时间一遍遍加 cout 打印。我更推荐先用纸笔走一遍样例让代码在脑子里逐行执行通常都能发现逻辑漏洞。真的需要调试的时候有一个技巧不要打印整个数组而是打印关键变量在关键步骤的值比如每次循环的 left 和 right、当前窗口的最大长度。这样输出量少定位快不容易把错误信息淹没在海量输出里。如果代码提交后结果不对优先检查三个地方数组越界尤其是left、right加减的时候。初始值设置错误比如0和1的混用。数据类型的溢出特别是累加、乘法运算中int不够用要换long long。4.4 时间分配策略先稳后难一套B卷大概率包含选择和编程题。我的策略是先把有把握的选择题快速做完不会的先跳过不要死磕编程题先看整体挑思路最清晰、最确定能做出来的那一道先做拿满一道的分比三道题都只写一半加起来的分还要多。编程题实在做不出来也要把暴力解法写上。很多笔试平台是按测试用例给分的暴力解法如果数据量小也能过一部分用例哪怕复杂度高拿到20%到40%的分也比你交个空代码强得多。另外写上注释说明你的思路即使没有完全通过后续面试官看代码也会对你有一定正向评价。还有一个小技巧如果题目要求输出结果并对某个大数取模记得在运算过程中就取模不要在最后乘完再取否则中间结果可能溢出。这个在动态规划的题目里特别常见。5. 备考方向的几个额外建议如果你正在准备这类算法笔试除了刷题还有几件容易被忽略的事。第一是认真看目标公司的业务。映客是做直播的它的算法工程师要处理的数据大多是实时流式数据所以你可以平时拿直播业务练手思维比如“如何用滑动窗口统计一段时间内的热门弹幕关键词”这类问题既能训练算法思维又能在面试自我介绍时体现出你对业务的理解。第二是动手梳理一遍自己的错题集。刷题的效果不在于数量而在于复盘。你会发现很多错误是重复的比如边界条件判断、下标偏移、溢出处理。把这些高频错误集中记下来考前30分钟看一遍比多做一套题有用得多。第三是按“笔试模式”训练而不是“刷题模式”训练。刷题的时候我们习惯了一题一题慢慢想但笔试是限时、限环境的。建议考前每周做两套全套的模拟题严格按90分钟时间内完成中间不查资料、不暂停。让自己适应这种紧张状态下输出代码的感觉考场上会从容很多。最后再说一个我在实际交流中发现的现象很多同学做完一套笔试后只关心自己过了没有很少去复盘整套卷子的命题思路。其实笔试题目本身就是一个很好的学习素材它反映了这家公司当前最看重的算法能力方向。做完一套题花半小时复盘一下哪些题做错了错在基础概念还是代码实现如果这个考点再换一个壳我还能不能做出来这些问题的答案才是这套卷子留给你的最大价值。
返回列表