每年九、十月份,各大厂的校招笔试就像一场全国范围内的打怪升级,“vivo 2020届校招在线编程笔试A卷”这个名字,在当年牛客网和知乎上被刷了不少存在感。我自己也参加过同一届的线上笔试,当时用的就是标准的在线编程评测系统,题目风格和牛客网真题卷高度接近,做完之后最大的感受是:它考的不是你会不会写某道题的答案,而是你在有限时间内能不能稳定地把一个工程问题拆开、建模、实现、调通。
这篇内容就围绕这套A卷展开,把当时大家在笔试群里讨论过的高频考点、在线编程平台的评测机制、还有我踩过的坑一并整理出来。适合正在准备校招笔试的应届生、想跳槽去终端厂商做技术岗的开发者,以及单纯想搞懂“在线编程笔试到底怎么筛人”的在校生。哪怕你还没到求职季,提前理解这套玩法也不亏。
1. 校招在线编程笔试到底在考什么
1.1 vivo这类终端厂商的笔试有什么特点
先说一个容易被忽略的事实:vivo虽然是手机厂商,但校招笔试的在线编程题并不偏硬件,反而非常偏基础算法和代码实现。A卷的题目范围大体集中在数组、字符串、模拟、动态规划、贪心这几类,偶尔会有一道和图论沾边的题。这是因为校招岗位面向的是软件研发、算法、测试等方向,笔试的目的不是筛选谁刷题多,而是看你能不能把问题抽象成数据结构与算法模型,再用代码实现出来。
另一个特点是,这套题整体难度在中档偏上,前一两道相对友好,后面的题会拉开差距。我当时做题的顺序是先扫一遍全部题目,把第一道模拟题和一道字符串处理题做掉,再啃动态规划,最后留时间处理最难的那道。这个策略在AC率和心态管理上都很重要。
和互联网大厂动辄四道题、每道题都要最优解的“地狱模式”相比,vivo A卷更看重稳定输出的能力。就算你不会特别高级的算法,只要把基础题做扎实、把边界条件处理好,也能拿到不错的分数。它是在筛选能干活、逻辑清楚的人,不是筛选竞赛选手。
1.2 A卷的常见考核维度与题型分布
从当年线上笔试的实际反馈来看,A卷大体可以归纳为以下四类考核维度:
本人记忆力有限,没法把每道原题逐字复现,但下面这些题型和考法是大家在笔试群里复盘时公认的高频方向,非常具有代表性。
| 题型方向 | 常见考法 | 核心知识点 |
|---|---|---|
| 模拟题 | 按规则模拟多轮流程,比如排队、洗牌、机器人走路 | 状态机、循环终止条件、数据结构选择 |
| 字符串处理 | 单词反转、子串匹配、括号匹配 | API熟练度、边界处理、栈的使用 |
| 动态规划 | 背包变体、最长子序列、路径计数 | 状态定义、转移方程、空间优化 |
| 贪心与排序 | 活动安排、区间合并、最小差值 | 贪心证明、排序规则设计 |
这里想提醒一句:模拟题虽然看起来“简单”,但它反而是失分重灾区。原因是模拟题往往细节多,循环条件稍微写错一个等号,或者没有搞清楚“先判断再执行”还是“先执行再判断”,整道题就会挂掉。在线编程没有人工看代码的过程,评测机只认输出,所以你必须靠自己在本地把各种情况测到位。
1.3 为什么公司宁愿用在线编程笔试来筛人
很多同学会问:我项目经历不错,简历上写了挺多东西,为什么还要做在线编程笔试?其实原因很现实。简历上的项目经历可以包装,但代码能力很难在短时间内伪装。在线编程笔试提供了一个统一、客观的筛选维度:不管你是985还是普通双非,同样一套题,同样的时限,同样的评测标准,代码一跑就知道水平。
更重要的是,在线编程笔试测评的是“你在有压力的环境下能不能搞定一个明确的需求”,这和实际工作非常像。工作里经常会遇到一个需求,边界条件模糊、数据量大、要和其他模块对接,而你需要在截止时间前给出可用的实现。笔试现场你也会面临类似的情况——时间有限、样例有限、还没有人帮你排查,全凭你平时积累的代码直觉和工程习惯。这就是为什么公司愿意在校招流程里保留在线编程笔试这个环节,它筛掉的不是“不会刷题”的人,而是“遇到问题项目就束手无策”的人。
2. 典型题型拆解:从审题到AC的完整思路
2.1 数组与模拟类题目:先把流程理顺再写代码
数组和模拟类题目通常是A卷的前两题,也是大家最容易犯“眼高手低”错误的题。这类题不会涉及太高深的算法,但会设计出一套规则,要求你模拟一个过程。比如一个典型的考法是:给定一个初始数组,每一轮按规则生成新数组,重复若干轮后输出结果。
遇到这种题,我通常建议先不写代码,先在草稿纸上把一轮操作的手动过程推演一遍。搞清楚几个关键问题:
- 这一轮操作是原地修改,还是生成新的数组?
- 每一轮之间有没有状态残留?
- 循环结束条件是达到指定轮数,还是数据收敛?
以“生成新数组”为例,如果直接在原数组上修改,可能会影响接下来的计算。正确做法是开辟一个新数组存储本轮结果,然后整体覆盖或交替使用两个数组。这种“空间换正确性”的手法虽然会多占用一份内存,但在笔试场景下,数据规模通常不会大到不可接受,换取正确性是完全值得的。
另外,模拟题还需要特别注意下标边界。很多题目从1开始计数,但数组索引从0开始,这种换算最容易出错。我的习惯是在代码里保留题目原始下标含义,定义变量名时直接带上语义,比如roundIndex、currentValue,不要让代码变成一堆i、j、k,否则写着写着就晕了。
2.2 字符串处理类题目:注意边界和语言API的差异
字符串处理题是A卷的常客,但这里的“常客”不等于“送分”。很多字符串题看起来简单,实际上一堆边界条件没有处理好,样例通过率就上不去。举一个高频的例子:反转句子中的单词顺序,比如输入"hello world vivo",输出"vivo world hello"。这个题如果不允许使用额外的split库函数,就需要自己遍历字符串,按空格切分单词,再倒序拼接。
这类题最容易踩的坑有三个:
- 字符串开头、结尾、中间有多个连续空格,如何处理?
- 逆序之后,单词之间的空格数量要不要保持一致?
- 统一用空格分隔时,收尾是否多了一个空格?
如果使用C++的istringstream,连续空格会被自动忽略,这在很多题目里没问题,但如果题目要求保留原始空格格式,就必须自己手动解析。用Java的话,String.split在处理" "时会有前导空串问题,需要用正则或先trim。用Python则要记住split()和split(' ')行为完全不同,前者会自动合并连续空白,后者不会。
另外一个容易被忽略的点是字符串匹配类题目的复杂度。如果在长度为n的文本里查m个模式串,用暴力匹配是O(n*m),笔试数据一大就会超时。这时就得考虑KMP或者前缀哈希。很多同学总觉得笔试考KMP不太可能,但实际上这类题目只要把数据规模稍微调大,暴力算法就会TLE,逼着你去用更优的解法。
2.3 动态规划与状态压缩:笔试拉开差距的核心
动态规划在vivo A卷里几乎是必考的。它的题型非常广泛,常见的有背包问题、最长上升子序列、编辑距离、矩阵路径数等。这类题目的难点在于:你能不能用数学语言把问题描述成“状态”和“转移”,而不是靠直觉硬写。
举个例子,一个经典变体是:有一个容量为W的背包,有若干个物品,每个物品有体积和价值,问如何装能获得最大价值。基础版本很简单,但如果加上“每个物品只能选一次”或“能选无限次”,状态转移方程就完全不同。一位一维数组优化的时候,背包问题的内层循环是正序遍历还是倒序遍历,这是很多人的失分点。
这里我分享一个实际处理动态规划题的小技巧:在写代码之前,先在注释里把状态定义和转移方程写出来,再动代码。比如:
// dp[i][j] 表示前i个物品,容量为j时的最大价值 // 第i个物品不选:dp[i][j] = dp[i-1][j] // 第i个物品选:dp[i][j] = max(dp[i][j], dp[i-1][j - weight[i]] + value[i])把这两行写在代码里,哪怕写到后面思路混乱了,也能靠注释快速拉回主线,而且如果最后要跟面试官讲思路,这份代码就是最好的讲解稿。这种习惯我在笔试复盘后一直保留,到了实际项目里写复杂状态机也很有用。
对于压轴级别的动态规划题,通常需要用到状态压缩、滚动数组或数据结构优化。如果你的目标是进入下一轮面试,建议把常见的状态压缩套路提前过一遍,理解“状态用二进制位表示”的思想,比如旅行商问题的经典解法。虽然不一定会考到,但这类题一旦出现,往往是区分度最高的题。
2.4 图论与贪心:高频但不是必考的补充点
图论在vivo A卷里的出现频率不算最高,但一旦出现,常见考法是最短路径、最小生成树、拓扑排序和并查集。很多同学一看到图就慌了,觉得要写一堆复杂的邻接表和堆优化。实际上笔试里的图论题,数据规模通常不会特别大,用邻接矩阵加Floyd或者朴素Dijkstra也能过,关键在于你有没有快速判断出“这是图论题”的敏锐度。
我记得身边有朋友在笔试时遇到一道题,表面上是给出一堆城市之间的道路和费用,求从起点到终点的最小花费。这题的本质就是单源最短路径,但他想了两分钟没反应过来,直接当成模拟题去写,结果越写越复杂,最后超时。所以刷题的时候一定要锻炼“题型敏感度”,看到“最小代价”“最短时间”“最少步数”这些关键词,第一反应就应该是图论算法。
贪心算法在A卷里更偏向于作为一道题目的子问题出现,比如区间调度、按某个规则排序后取最优。这里要强调一点:贪心算法看起来简单,但必须能证明它的正确性,否则笔试里很容易画蛇添足。最简单可靠的证明方法就是“交换论证法”——假设最优解和贪心解在某个位置不同,通过交换元素证明贪心解不会比最优解差。哪怕你在笔试现场不写证明,心里也得有数,否则换个数据就过不去。
3. 在线编程环境的实战操作细节
3.1 常见笔试平台的输入输出模板
在线编程笔试和本地IDE写代码最大的差异就是输入输出。你不需要处理文件读写,也不需要考虑用户交互,评测系统会把数据通过标准输入传给你的程序,你只需要把正确结果输出到标准输出。听起来很简单,但每年笔试都有不少人因为输入输出格式处理不对而挂掉。
这里我整理几套常用的输入输出模板,不同平台(比如牛客网、赛码网)要求差异不大,但你要确保自己能默写不漏:
C++ 读入未知数量的整数:
#include <bits/stdc++.h> using namespace std; int main() { int x; while (cin >> x) { // 处理每一个整数 } return 0; }Java 读取一整行再解析:
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); while (sc.hasNextInt()) { int n = sc.nextInt(); // 处理数据 } } }Python 快速读入大量数据:
import sys data = sys.stdin.read().strip().split() # 然后按顺序解析 data 列表中的元素如果你平时主要用IDE练题,忽略了标准输入输出的处理,那在笔试开始前一定花一点时间把模板准备好。很多在线编程平台第一题就是让你读入若干个数字并求和,这种“送分题”如果因为读取失败只过了一部分样例,心态很容易崩。
3.2 时间分配与提交策略
在线编程笔试的时间通常是一个半小时到两个小时,题量在3到4道之间。很多人失败不是因为不会做,而是因为时间分配不合理,在前面的题上死磕太久,后面的题连看都没看一眼。
我建议的通用策略是:前5分钟先把所有题通读一遍,在草稿纸上记下每道题的难度判断和可能的算法方向。然后按以下优先级去做:
- 先做自己最擅长、思路最清晰的题,哪怕它在题目列表里不是第一题。
- 对于没思路的题,先跳过,别浪费超过15分钟。
- 做完所有能拿分的题之后,再回来啃难题。
- 每道题提交之前,先在本地测试几组边界数据,比如空数组、只有一个元素、最大值、最小值。
在线评测系统通常支持多次提交,但部分平台会因为你频繁提交失败而扣分。稳妥起见,除非完全确定,否则不要每改一行就提交一次。先在本地把所有想到的测试用例跑完,再一次性提交。
3.3 本地调试与在线评测的差异处理
本地跑得好好的代码,一提交上去就只对了一部分样例,这种情况在笔试里太常见了。原因往往不是算法出了问题,而是没有考虑多组测试用例的处理方式。
在线评测平台有两种常见的评测方式:一种是单组测试用例,题目输入里只有一组数据;另一种是多组测试用例,程序需要循环读取,直到输入结束。很多本地IDE的调试习惯是先给定一组输入,程序跑完就退出,但到了在线评测平台上就不行了。
如果你不确定题目是多组还是单组,最安全的方式是写成循环读取的格式,这样无论是单组还是多组都能适配。从结果上看,按循环读取方式实现的代码,在单组输入下也能正常输出,不会影响正确性。
还有一个常见差异是输出格式。在线评测系统通常只比对输出内容,不关心你代码里是否有额外输出,但如果你在本地调试时打印了中间变量,又没有删掉,提交上去就会导致“答案错误”或“格式错误”。建议笔试时把所有调试输出用注释包起来,或者写一个调试宏,在评测时自动关闭。
4. 实操过程中踩过的坑和排查方法
4.1 边界条件与数组越界
数组越界是C/C++选手最常犯的错误,而且越界了并不一定崩溃,很多时候程序会“恰好”跑出正确结果,直到你换了一组测试数据才暴露问题。这类问题排查起来非常痛苦,因为错误结果可能出现在程序的后面阶段,而不是越界发生的那一行。
我维护自己代码的常用方法,是每次在访问数组之前,先用断言或者条件判断确保下标在合法范围。比如:
if (idx < 0 || idx >= n) { // 打印日志,排查逻辑错误 continue; }在笔试场景下,这段代码可以帮你快速定位问题,而且因为你输出的内容不包含调试信息,最终提交前要记得清理。对于Java和Python,越界会直接抛出异常,所以这类问题更多出现在C++代码中,千万不能掉以轻心。
另外一个容易被忽略的边界是int范围。题目中给出10万级别的数据量时,很多中间结果可能超过2的31次方减1。使用 long long(或Python的默认大整数)能省去很多麻烦,在笔试里不用刻意追求空间上的极致节省,数据正确永远是第一优先级。
4.2 大数溢出与数据类型选择
数据溢出这个坑,可以说是我笔试过程中踩得最疼的一次。有一道题数据范围看似不大,单个数字在10的9次方以内,但状态转移时需要两个数相加,结果就超过了int的表示范围。当时本地测试用的小样例全部正常,一提交就出现连续几个用例答案错误。后来对比输出才发现是负数,才意识到是溢出。
自此之后,只要看到题目中的数字大于10的5次方,我就会优先使用64位整数类型。C++用long long,Java用long,Python则不用担心普通整型溢出,但要小心浮点除法带来的精度问题。在笔试现场,溢出问题一旦出现,调试成本极高,远远不如一开始就选对类型划算。
4.3 超时问题与复杂度估算
超时是另一种让人摸不着头脑的失败方式。代码跑了很久没结束,评测系统直接判超时,你不会拿到任何有效输出。面对超时,最需要的是快速估算复杂度:你的算法在最坏情况下运行多少次操作?1秒内通常能跑大约5乘10的7次方到10的8次方次基础运算,超过这个数量级就很可能超时。
所以每道题动手写之前,应该先看一眼数据范围,脑算一下自己打算用的算法的复杂度是否在可接受范围内。比如数据范围是10的5次方,两层循环就是10的10次方,必然超时,这时候必须想O(n log n)或O(n)的算法。如果你对复杂度没有概念,笔试现场会非常吃亏,因为你代码写完了才发现跑不动,等于白写。
这时候就需要掌握常见的优化思路:桶排序代替排序、前缀和代替区间重复计算、二分查找代替线性扫描、记忆化搜索代替重复递归等。这些优化技巧并不难,关键是在平时刷题时养成“写代码前先估算复杂度”的习惯。
4.4 内存使用与多组用例处理
有些笔试平台会限制运行内存,通常在256MB或512MB。如果你的代码开辟了过大的二维数组,比如int dp[10001][10001],那就是约400MB,直接内存超限。对于这种场景,要么把数组改成vector并配合动态分配,要么使用滚动数组优化空间。
例如经典背包问题里的空间优化:
vector<int> dp(W + 1, 0); for (int i = 1; i <= n; i++) { for (int j = W; j >= weight[i]; j--) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } }这里把二维数组压缩成一维,关键在于内层循环要倒序遍历,防止同一个物品被重复使用。这种优化不仅是内存层面的,也是很多动态规划题目的标准写法,值得刻意练习。
多组用例的处理也很容易出错。有的题目输入会先给一个T,表示有 T 组测试数据,你必须读取 T 之后循环处理。有的题目不给 T,而是以文件结束符为终止标识。如果把二者混为一谈,就会导致读取次数多了或少了,最终结果自然错误。我的做法是看到题面描述里出现“第一行一个整数T,表示测试数据组数”,就明确写一个for (int t = 0; t < T; t++),如果不确定,就用while循环读取,兼容性更强。
5. 笔试结束后如何复盘并衔接面试
5.1 从笔试结果反推薄弱点
很多人笔试一结束就去对答案、查分数,然后就不管了。我强烈建议做一次系统复盘,因为笔试暴露的问题,在后续面试里很可能还会被问到。复盘时从三个方向入手:
- 算法功底:有没有因为某个算法不熟悉导致题目没做出来?比如遇到动态规划完全没有思路,说明状态定义这块需要补。
- 代码实现:有没有思路正确但代码写错的题?这通常是边界处理、类型选择、输入输出格式的问题。
- 时间管理:有没有因为时间不够而没来得及看的题?这说明前面的题耗时太长,需要加强做题速度和策略。
复盘不是简单把题重做一遍,而是去对比参考解法和你自己解法的差距。如果参考解法用了更简洁的数据结构,那说明你对 STL 或 Python 内置库还不够熟悉;如果参考解法有额外的边界判断,那说明你的逻辑不够严密。这些问题在面试手撕代码环节都会再次暴露。
拿我自己来说,当年A卷里有一道动态规划题我没做出来,复盘时才发现问题出在状态定义上,我用二维数组记录“当前值”,但参考解法用一维数组加一个前缀最大值就搞定了。这个差距让我认识到,不仅要把题做对,还要尝试找到更优的设计,这种思路在后来面试中帮了我大忙。
5.2 把笔试代码改造成可讲的面试项目
多数人不知道的是:笔试里做出来的题目,可以被改造成面试中手撕代码的素材。比如你在笔试中实现过一个“按规则模拟多轮流程”的题,你在面试中就可以讲自己如何处理复杂流程的状态管理、如何设计数据结构来减少遍历次数、如何对边界条件做异常保护。这些问题比背一个八股答案有说服力得多。
具体操作建议是:笔试结束后,把每道自己AC或部分AC的题整理到自己的代码仓库里,注释里写清楚思路、时间复杂度和易错点。面试前把这些题重新写一遍,不看原来的代码,只凭注释里的思路去复现。这个过程能有效检验你是不是真的掌握了解法,而不是当时刚好灵光乍现。
如果你的简历上写了“熟悉算法与数据结构”,面试官很可能要求现场手写代码,这时候拿笔试真题练手比随机刷题更有效。因为你已经知道这套题的考察重点和风格,能在短时间内快速调用对应算法,真正进入面试时会更从容。
另外,笔试中发现的快速输入输出、复杂度估算、数组越界检查这些工程习惯,在面试手撕代码时同样重要。你可以在面试时主动说出“这道题数据范围较大,我选择用 long long”或“这里使用滚动数组优化空间”,会让面试官觉得你有工程意识,而不只是会写算法题。
最后分享一个我个人的小习惯:每次在线编程笔试结束后,不管成绩如何,我都会写一段几十字的心得,记录这次笔试遇到的问题和当时的心态。攒了几场之后回头看,能明显看到自己的进步曲线。校招季很长,笔试不止一场,别让一次失败影响后面的节奏,把每一场笔试当成下一次的预演,会轻松很多。