vivo校招笔试B卷复盘:算法题实战思路与时间分配策略

发布时间:2026/8/31 4:33:58
vivo校招笔试B卷复盘:算法题实战思路与时间分配策略 2020届校招季不少同学是第一次接触在线编程笔试这种模式。网页里嵌着一个没有代码补全、没有自动保存的编辑器摄像头开着屏幕上三四个题目限时150分钟。你刷了半年LeetCode以为自己准备好了结果第一道题就卡在“怎么把题目转化成算法模型”上憋了二十分钟才动手最后样例都没跑完就被系统收卷了。这种体验参加过vivo 2020届校招在线编程笔试B卷的同学应该都不陌生。这篇文章不是单纯讲某道题的标准答案而是想带你完整复盘一下B卷到底考什么、做题时应该怎么分配时间、哪些地方最容易莫名其妙扣分以及笔试结束后怎么把这三道题变成你后续面试的素材。内容基于同批次笔试反馈整理出的等价题型来拆解不是逐字复刻原题但考点、难度和坑都是同一批的。准备校招算法岗、开发岗的同学都可以按这个思路来备考。1. B卷到底在筛什么人——先看懂这场笔试的筛选逻辑1.1 在线笔试在校招流程中的真实定位校招的完整链路一般是投简历、在线笔试、技术一面、技术二面、HR面。笔试处在简历筛选之后、面试之前它的定位不是“选大神”而是“过滤”。什么意思笔试题目通常不会刻意出到ACM区域赛难度因为它要的不是百里挑一的竞赛选手而是把“数据结构基础不牢”“代码能力不过关”“连题目都读不懂”的候选人筛掉。尤其是B卷这种平行卷和A卷题面不同、难度对齐本质是同样的筛选标准。所以你会看到B卷的三道题通常呈梯度分布一道签到题考察基本编码能力大概LeetCode简单到中等难度一道中等题需要一点算法思维比如双指针、贪心、前缀和一道较难题用来区分那些“刷题量大”和“真正理解算法”的人。三道题全AC的人凤毛麟角但AC一题半、另一题拿部分分的人依然有机会进面试。这告诉你一个关键策略笔试拼的不是满分而是相对排名。你不需要吃透所有题但必须保证“该拿的分一分不少”。1.2 换壳不换核业务包装背后的算法考点vivo的业务场景包括手机、IoT、应用商店、售后客服、供应链管理所以笔试题很爱把这些真实场景包装进题面里。比如“应用商店每天产生海量日志要求查找包含全部26个字符的最短连续片段”——本质是最小覆盖子串问题“客服一天收到若干工单每个工单有开始时间和结束时间一个客服同时只能处理一条”——本质是区间调度贪心问题“道路上有一批写字楼需要选择若干位置部署服务器求覆盖所有楼的最小半径”——本质是二分答案加贪心判定问题。很多同学第一眼看到这种题就慌了觉得业务背景复杂。但你只要把“日志片段”还原成“字符串”把“工单”还原成“区间”把“服务器覆盖半径”还原成“分组跨度最小值”题目立刻就变成了你刷过的经典模型。这是笔试和LeetCode最大的区别之一LeetCode题目一般已经把数据结构和算法问法写得很直白而笔试题目需要你先做一步“脱壳”。我在模拟面试时经常和同学说读题的前三分钟不是用来写代码的而是用来把题面翻译成算法术语的。翻译对了这道题就完成了一半。1.3 刷题量与笔试通过率不成正比的原因经常有人问我“学长我LeetCode刷了三百题为什么校招笔试还是挂”原因往往不在“量”而在“习惯”。LeetCode的练习环境和真实笔试环境差别很大。LeetCode有即时判题、有错误用例提示、有讨论区你可以一遍遍试错笔试系统通常只有一个简陋的编辑器没有自动补全提交后只告诉你AC还是WA没有具体错误信息有时样例都不能反复跑。更关键的是LeetCode你可以在任何时候暂停、查资料、想半小时再看答案但笔试是倒计时的每一分钟都在流逝。所以备考不能只刷题还要模拟“一次成型”的能力。我建议至少做十次限时模拟笔试用和真实考试一样的环境不查资料、不中途看答案、全程计时。习惯了这个节奏你才能在真正的笔试里稳定输出。2. 三道典型题的实战拆解从读题到AC的完整思路这里用三道等价题型来还原B卷的常见考点。题目不是原题复刻但考点覆盖了当年B卷里几类高频题型。如果你能把这三道题独立写出来笔试的基本盘就稳了。2.1 第一题应用商店日志里的最小覆盖串题面vivo应用商店每天产生海量日志每条日志都有一个由小写字母组成的版本标识串。运营希望找出一个连续片段使这个片段里26个英文字母都至少出现一次并输出最短片段长度。如果不存在这样的片段输出-1。输入格式第一行一个整数n表示字符串长度第二行一个长度为n的字符串s只含小写字母。 输出格式一个整数表示最短覆盖子串长度如果不存在输出-1。示例输入 26 abcdefghijklmnopqrstuvwxyz 输出 26输入 6 aabbcc 输出 -1思路演进先别急着写代码想清楚暴力怎么做枚举所有子串对于每个子串检查是否包含26个字符时间复杂度O(n²)字符串长度一万以内还能扛但笔试数据范围常常给到10^5甚至10^6暴力必挂。优化的核心是“滑动窗口”。我们用两个指针left和right维护一个窗口right不断向右扩展把新字符加入窗口当窗口内已经集齐26种字符时记录窗口长度然后尝试把left向右收缩看能不能在仍然集齐的情况下缩短窗口。这个过程中始终只需要维护一个长度为26的计数数组和一个“当前窗口内有多少种不同字符”的变量。为什么不用哈希表而用数组因为字符范围只有26数组访问是O(1)且没有哈希碰撞开销笔试环境里这是更稳妥的选择。参考代码C#include iostream #include string #include climits using namespace std; int main() { int n; string s; cin n s; int cnt[26] {0}; int types 0; // 窗口内不同字符的种类数 int left 0; int ans INT_MAX; for (int right 0; right n; right) { // 右指针扩展窗口 if (cnt[s[right] - a] 0) types; cnt[s[right] - a]; // 已集齐26种尝试收缩左边界 while (types 26) { ans min(ans, right - left 1); int idx s[left] - a; cnt[idx]--; if (cnt[idx] 0) types--; left; } } cout (ans INT_MAX ? -1 : ans) endl; return 0; }复杂度与易错点时间复杂度O(n)空间复杂度O(1)数组固定26个元素。这道题最常犯的错误有两个。第一个是只统计字符出现次数而忘记统计“种类数”导致收缩条件写错第二个是收缩左边界时先收缩再更新答案导致漏掉了某些窗口长度。另外注意题目要求的是“连续片段”所以滑动窗口天然契合如果是子序列解法就完全不一样了读题时一定要看仔细。2.2 第二题客服工单调度问题题面vivo售后客服平台一天内收到n条工单每条工单有开始时间start和结束时间end客服在同一时间只能处理一条工单。工单在结束时刻就会释放所以如果上一条在t时刻结束下一条可以在t时刻开始。求一个客服一天最多能处理多少条工单。输入格式第一行一个整数n接下来n行每行两个整数start和end。 输出格式一个整数表示最多能处理的工单数。示例输入 5 1 3 2 5 3 6 5 7 6 8 输出 3解释可以依次选择[1,3]、[3,6]、[6,8]这三条工单共3条。思路演进这是经典的“活动安排问题”也是区间贪心里最典型的模型。为什么能用贪心关键在于排序策略。你可能直觉上会想按开始时间排序或者按区间长度排序。但正确的做法是按结束时间升序排序然后依次遍历只要当前工单的开始时间不早于上一次处理的结束时间就选择它。证明思路不复杂假设所有工单里结束时间最早的是E那么选择E一定不会比选择任何其他工单差。因为E占用的区间不会比别的工单更长而且结束得更早给后续工单留出的空间更大。既然每一步选择局部最优都不会影响全局最优贪心就是安全的。参考代码C#include iostream #include vector #include algorithm using namespace std; struct Order { int start, end; }; int main() { int n; cin n; vectorOrder orders(n); for (int i 0; i n; i) { cin orders[i].start orders[i].end; } // 关键按结束时间升序排序 sort(orders.begin(), orders.end(), [](const Order a, const Order b) { return a.end b.end; }); int count 0; int lastEnd -1; for (const auto order : orders) { if (order.start lastEnd) { count; lastEnd order.end; } } cout count endl; return 0; }复杂度与易错点时间复杂度O(n log n)主要花在排序上空间复杂度O(n)。这道题的坑集中在两个地方。第一个是结束时刻的处理题面明确说“结束时刻释放”所以允许start等于上一个end如果题面改一个词变成“工单结束前后需要留出交接时间”逻辑就要改成start lastEnd这就是为什么每次都要看清题面措辞。第二个坑是lastEnd的初始值设成-1是一种比较稳的写法避免边界麻烦如果你设成0而工单最早开始时间恰好是0就会漏掉第一条工单。2.3 第三题办公楼网络覆盖的最小半径题面一条笔直道路上有n栋写字楼需要接入企业网络第i栋楼的坐标是a[i]。现在需要在道路上选择k个点位部署边缘服务器每个服务器可以覆盖半径R的连续区间也就是说服务器部署在x位置能覆盖坐标在[x-R, xR]范围内的所有楼。所有服务器的覆盖半径相同。求最小的整数R使得所有楼都能被至少一个服务器覆盖。输入格式第一行两个整数n和k第二行n个整数a[i]。 输出格式一个整数表示最小的半径R。示例输入 5 2 1 2 3 4 5 输出 1解释两台服务器一台放在2附近覆盖1、2、3一台放在5附近覆盖3、4、5半径1就够。输入 5 1 1 2 3 4 5 输出 2解释一台服务器放在3半径2覆盖1到5全部。思路演进这道题难度明显上来了难不在代码而在你能不能想到“二分答案”这个方向。先说直觉。如果R很大比如从第1栋楼到第n栋楼那么远那一定可以覆盖如果R很小比如0那每个服务器只能覆盖自己所在位置那一栋楼只有当k≥n时才可能成立。随着R从小到大递增可行性是单调变化的R越大越容易覆盖全部。这个“单调性”就是二分答案的入场券。具体做法分三步对所有楼坐标排序在区间[0, a[n-1] - a[0]]里二分R对于每个R写一个check函数判断“用k个服务器能否覆盖全部”。check函数的贪心逻辑从左到右扫描遇到第一个未被覆盖的楼a[i]就把一台服务器放在a[i] R处。这样这台服务器能覆盖的楼的范围是[a[i], a[i] 2R]因为服务器的左端点正好压在a[i]上向右延伸半径R所以覆盖右边界是a[i] 2R。然后继续往后扫直到所有楼都被覆盖或者服务器数量超过k。为什么这样放置是最优的因为你从最左边未覆盖的点开始考虑这台服务器无论如何都得覆盖a[i]为了让“向右覆盖尽可能远”只有把服务器往右放左边界正好顶到a[i]才能最大化覆盖范围。任何其他放法能覆盖的右侧范围都不会超过这个方案。参考代码C#include iostream #include vector #include algorithm using namespace std; int main() { int n, k; cin n k; vectorint a(n); for (int i 0; i n; i) cin a[i]; sort(a.begin(), a.end()); auto can [](int R) - bool { int servers 0; int i 0; while (i n) { servers; if (servers k) return false; // 服务器放在 a[i] R 处覆盖到 a[i] 2R long long reach 1LL * a[i] 2LL * R; while (i n a[i] reach) i; } return true; }; int left 0; int right a[n - 1] - a[0]; while (left right) { int mid left (right - left) / 2; if (can(mid)) { right mid; } else { left mid 1; } } cout left endl; return 0; }复杂度与易错点排序O(n log n)二分O(log(range))次每次check是O(n)总复杂度O(n log n n log(range))。range最大值是坐标跨度笔试里坐标可能到10^9所以二分次数大概三四十次完全可接受。这道题容易翻车的细节有三处。第一二分判定方向容易写反can(mid)为true说明R可能还能更小所以要收缩右边界而不是左边界。第二reach要用long long存因为a[i] 2R可能超过int范围第三如果k≥n答案直接是0但上述二分逻辑也能兼容因为R0时每个点单独一台服务器就够了。唯一要注意的是输入数据允许k大于n时while循环不会出现servers k的情况。3. 在线笔试系统里最容易丢分的三个隐性环节很多同学题目思路完全正确代码一看也没问题但提交就是WA甚至RE。问题往往不在算法本身而在在线笔试系统的使用细节上。3.1 输入输出你以为的格式和评测机理解的格式校招笔试的输入输出格式五花八门但最常见的坑是“单组数据”和“多组数据”的区别。有的题目只给一组输入你直接读一次就行有的题目会写“输入包含多组测试用例每组第一行是n”这种就必须用循环读取到文件末尾对应C里是while(cin n)对应Java里是while(scanner.hasNext())。还有一个高频坑是读取字符串时的空行。如果题目要求先读一个整数n然后读一行字符串你用cin n之后再调用getline会读到那一行残留的换行符导致字符串为空。我见过无数考生在这里翻车。稳妥的做法是如果下一行是字符串在cin n之后先加一句cin.ignore()吞掉换行或者统一都用cin 不混用getline。性能也是个大问题。如果你是C选手cin和cout默认会和C标准IO同步导致读写很慢数据量大的时候直接TLE。最省事的处理是在main函数开头加两行ios::sync_with_stdio(false); cin.tie(nullptr);这样就能让cin和cout接近printf、scanf的速度。比这更稳的是直接用scanf和printf但很多刷LeetCode刷习惯的同学已经不太会写C风格IO了那就把同步关闭记住。3.2 性能陷阱大部分WA不是逻辑错而是超时在线笔试系统判题时会把超时分为TLE但很多同学不知道自己AC不了的原因不是算法不对而是常数太大。C在普通在线评测环境里一秒钟大概能执行10^8次简单运算。如果你的算法复杂度是O(n²)而n是10^5那就是10^10次运算怎么优化常数都救不回来。反过来如果n是10^3O(n²)完全没问题。所以读题时一定要看数据范围根据范围反推复杂度要求。另一个隐形陷阱是递归爆栈。笔试里有些题用DFS做很自然但递归层数过深会导致堆栈溢出。比如n10^5的线性DFS默认栈空间根本不够。解决办法是要么改成显式栈迭代要么在允许的情况下把递归改成循环。很多编译器支持扩大栈空间但笔试环境里你没有这个权限。第三类性能陷阱是STL容器的不当使用。比如频繁调用vector的insert、erase或者用字符串拼接循环这些操作看着人畜无害实际复杂度很糟糕。还有一个冷知识如果题目数据是特判过的unordered_map可能被碰撞攻击退化到O(n²)这种情况下用map红黑树反而稳定或者干脆用数组计数。3.3 本地能跑、提交就不对的边界条件这是最让人崩溃的情况代码在自己电脑上跑得很好样例输入输出全部正确一提交就是WA。原因通常是边界情况没处理。最常见的几个bug源头数组越界访问了a[-1]或a[n]C不会在运行时报错而是读到脏数据导致结果诡异。排查方法是在循环边界加断言或者先用小数据自己在纸上推一遍。除零错误做除法或取模前没判断分母是否为0。整数溢出两个int相乘结果用int存溢出后变成负数。笔试里碰见坐标、次数、乘积优先考虑long long。多组数据不清空全局变量如果你定义了全局数组或计数器一组数据处理完没重置下一组数据就会带着上一组的残留结果跑。空输入n0时程序怎么表现很多人的代码在n0时直接越界访问a[0]如果题目没说n一定大于0就要做兼容。这里分享一个习惯每道题写完除了样例再自己补三组测试——最小输入n1、最大输入n取上限、极端分布全部相同或全部升序。这三组过了这道题就稳了一大半。4. 时间分配与做题策略如何把150分钟花在刀刃上4.1 拿到试卷的前两分钟不是用来写代码的我见过太多同学打开题目就开始敲代码敲到一半发现理解错题意又全部删掉重来。正确做法是前两分钟快速通读所有题目给每道题打标签考察什么数据结构、数据范围多大、大概是什么难度、自己有没有把握。这个动作很重要。它能帮你建立全局观避免在前一道难题上耗太久导致后面简单的签到题没时间写。通读完你心里应该有一个排序哪道题是必拿分哪道题是争取拿分哪道题实在不行就写暴力。以150分钟三道题为例理想的节奏大概是签到题控制在30分钟以内中等题控制在50分钟左右难题先想20分钟没有思路就写部分分最后留20到30分钟通查。这里的时间不是死的但有一个原则必须坚持状态最好的前60分钟一定要用来做“最有把握的题”先保住基本盘。4.2 学会了最优解也要学会写部分分笔试不是只有0分和AC两种结果很多系统按用例比例给分。比如第三题你想不出二分答案但你知道暴力枚举R从0往上试每次验证能不能覆盖那也能拿一部分分数。我经常和同学说难题的得分策略是能写出正确但复杂度高的代码也好过交一份空题。因为部分分可能是一半甚至更多而空题就是0。具体怎么抢部分分第一如果题目范围小比如n≤1000直接写暴力或模拟不搞优化第二如果数据范围大但你可以用简单方法覆盖一部分特殊情况比如k≥n时输出0或者所有楼都一样时输出0这些特殊情况也可能对应测试用例第三哪怕你只读懂了题面也把输入输出框架写好把最简单的样例跑通至少不会得0分。4.3 卡壳超过十五分钟立刻止损笔试里最浪费时间的不是不会做而是“觉得快想出来了再想五分钟”。这个五分钟可能会变成五十分钟。我的建议是硬性止损一道题如果连续思考15到20分钟还没有一个可落地的思路马上停笔先去做其他题。等你把其他题做完回来大脑的无意识加工可能已经帮你理出思路了。即使没有你的整体分数也因为你没有空着其他题而更高。这里有个心理层面的原因在线笔试的倒计时会放大焦虑越焦虑越容易钻牛角尖。换题是一种主动打断焦虑的方式能让你从死胡同里退出来重新建立节奏感。4.4 提交前五分钟的“体检清单”每次提交前按这个清单过一遍能拦住九成低级错误有没有删掉调试用的cout或printf输出数组开的大小是否足够下标是否会越界数据范围有没有超过int需不需要用long long输出格式对不对有没有多余的空格或换行全局变量在多组数据场景下是否已重置这一步看起来简单但高压状态下特别容易漏。我在真实笔试里就因为没删一句调试输出白白丢了一道签到题的分从那以后每次提交前都强迫自己按这个顺序过一遍。5. 笔试结束不是终点复盘如何反哺后续面试5.1 笔试题其实是最好的模拟面试素材很多人笔试结束就彻底放下开始刷下一套题。但实际上校招面试里有一个高频问题“你笔试第三题是怎么做的有没有更优解”如果你能把自己笔试时的思路、卡点、如何优化的过程讲清楚这比背一道LeetCode原题更能打动面试官。所以我建议每场笔试结束后两小时内趁记忆还热立刻做一次复盘。不需要写长篇大论按“题面核心考点、我的解法、复杂度、我卡在哪里、正确答案的思路、边界用例”这六项记录就行。这份文档就是你后续面试前最好的复习资料。5.2 一题多解让面试官觉得你是真的懂复盘时别只满足于“AC了就结束”。试着问问自己这道题还有没有其他解法各个解法之间是什么关系比如上面第一题最小覆盖子串除了双指针还可以对每个右端点二分查找左端点能缩到哪第二题工单调度除了贪心还能用动态规划做但n大的时候贪心才是最优解第三题服务器覆盖二分答案的本质是把“最优化问题”转成“判定问题”这类思维方式在面试里非常加分。我自己的体会是一题多解不是为了炫技而是让你真正理解算法的适用边界。当你把一道题从暴力到优化、从一种思路到另一种思路全部捋清楚面试官一问你就能脱口而出根本不用临时组织语言。5.3 高频考点清单按这份清单备考更高效根据B卷和同类校招笔试的题目分布我把高频考点按优先级整理成了下面这张表你可以对照着查漏补缺考点类别常见题型备考建议数组与字符串双指针、滑动窗口、前缀和高频中的高频至少各刷5道经典题哈希表统计频次、去重、两数之和变体熟练运用unordered_map但注意碰撞场景排序与贪心活动安排、区间合并、最小生成树思想重点是证明贪心策略的正确性二分答案最小化最大值、最大化最小值掌握从“求最优解”到“验证可行性”的转化栈与队列单调栈、单调队列、括号匹配处理“下一个更大元素”“滑动窗口最大值”搜索BFS、DFS、拓扑排序注意递归爆栈熟悉迭代写法动态规划背包、最长子序列、区间DP校招笔试出现频率很高但常放在较难题位置并查集连通性判断、冗余连接代码短但思路巧妙值得付出时间堆TopK问题、合并K个有序列表理解priority_queue的使用与复杂度这中间双指针、贪心、二分答案、动态规划四类是重中之重。B卷的难度分布也主要集中在这四类你把这四块吃透通过笔试的概率会大幅提升。最后说点个人经验。校招笔试这件事短期看刷题量长期看复盘质量。我见过刷题量不高但每场笔试后认真写复盘文档的同学最后进了很好的公司也见过刷了五六百题、但每次考完就丢掉的选手笔试挂了一轮又一轮。真正拉开差距的不是你会不会做某道题而是你能不能稳定地把会做的题全部做对并把每场笔试都变成下一场的垫脚石。B卷的三道题只是起点从这场笔试里学到的读题方法、时间管理、边界意识才是你能带走的最有价值的东西。