又到一年校招季,后台收到不少准备投递手机厂商的同学问笔试怎么准备。今天就以vivo 2020届校招在线编程笔试A卷为切入点,聊聊这类在线编程笔试到底在考什么、怎么答才能拿高分。我当年也是从这套卷子一路走过来的,后来帮学弟学妹改简历、做模拟面试,对这类题型的套路已经摸得比较透。这篇文章不打算贴原题答案(网上也找不到完全可信的版本),而是把试卷背后的出题逻辑、高频考点、答题节奏、常见坑位一次讲清楚。不管你是2025届还是2026届,只要目标是手机厂商、互联网大厂的校招研发岗,这份拆解都值得你花十分钟看完。
1. 这场笔试在考什么:题型结构与整体设计思路
1.1 在线编程笔试的典型构成
先说一个很多人忽略的事实:vivo这种手机厂商的在线编程笔试,和互联网大厂的题风格不太一样。大厂喜欢出偏竞赛思维的题,绕好几个弯才能想到最优解;手机厂商更看重工程落地能力和代码基本功,题目往往背景清晰、需求直白,但会在边界条件和数据规模上埋雷。
2020届A卷整体分两部分:客观题和编程题。客观题通常是选择题,覆盖计算机网络、操作系统、数据结构、Java/C++语言基础等,这部分考察的是计算机基础是否扎实。编程题一般是2到3道,难度呈梯度上升,从“送分题”到“区分题”,最后一道通常有区分度,用来筛选真正有算法功底的人。
在线编程的平台一般是牛客网或者赛码网,支持的语言比较全,C/C++、Java、Python都能选。我建议你提前登录平台熟悉一下代码编辑器和提交方式,尤其是输入输出模板,别把时间浪费在调试本地环境上。
1.2 vivo笔试题目的出题倾向
从历年题目看,vivo笔试的编程题有几个明显偏好:
第一是数组和字符串操作出现频率极高。比如数组去重、排序后统计、字符串匹配、子串截取,这类题看着简单,但往往要求在一次遍历内完成,考察你的代码是否简洁高效。
第二是动态规划几乎年年必考。背包问题、爬楼梯变体、二维路径问题都很常见,而且通常作为压轴题出现。原因是DP既能考察建模能力,又能暴露代码功底,一道题就能筛掉大部分人。
第三是不太考复杂的图论和高级数据结构。像线段树、后缀数组、网络流这些,在手机厂商笔试里基本不会出现。出题人更关心的是你能否用基础的数据结构解决实际问题,这和企业日常开发的需求是匹配的。
所以备考策略很明确:把基础算法吃透,把常见题型的模板写好,比盲目刷难题有效得多。我见过太多同学天天刷LeetCode hard,结果笔试时连一道中等的DP都没写出来,就是因为基础不稳、模板不熟。
2. 核心考点逐类拆解:从送分题到压轴题
2.1 数组与模拟类题:先把逻辑理顺再动手
这类题在A卷里通常是第一道,目的很单纯:给大部分候选人送点分,也快速过滤掉完全不会写代码的人。典型的题目背景可能是“给定一个整数数组,统计出现次数前k大的元素”或者“将一个数组按奇偶顺序重新排列”。
我以“按奇偶顺序重新排列数组”为例,讲讲这类题的标准解法。题目要求把所有奇数放在数组前半部分,偶数放在后半部分,并且保持相对顺序不变。
最直观的做法是额外开一个临时数组,第一遍遍历收集所有奇数,第二遍收集所有偶数,最后合并回去。时间复杂度O(n),空间复杂度O(n),代码清晰,面试笔试都够用。
#include <vector> using namespace std; vector<int> reorderArray(vector<int>& nums) { vector<int> result; for (int num : nums) { if (num % 2 != 0) result.push_back(num); } for (int num : nums) { if (num % 2 == 0) result.push_back(num); } return result; }这里有个容易踩的坑:如果题目要求“在原数组上操作,不开额外空间”,就不能用上面的写法。原地版本用双指针,一个从前往后找偶数,一个从后往前找奇数,找到就交换。但这种方法会改变相对顺序,如果题目额外要求“保持相对顺序”,就需要用插入排序的思路,找到奇数后依次移动中间的元素,时间复杂度会变成O(n²)。
笔试时遇到这类题,先看清楚题目对空间和顺序到底有没有额外要求,再决定用哪种解法。我见过不少同学看到“数组重排”就默认双指针,结果因为改变顺序导致样例过不了,白白丢分。
2.2 字符串处理类题:边界条件决定生死
字符串处理是A卷的第二类高频题,常见的有:反转句子中的单词顺序、判断回文串变体、计算字符串中某个子串出现的次数等。这类题技术含量不高,但特别考验细心程度,边界条件处理不好,很容易出现“样例全过、提交0分”的惨剧。
举一个典型的例子:给定一个字符串,反转其中每个单词的字母顺序,但单词之间的相对位置不变。比如输入"hello world",输出"olleh dlrow"。
这道题的核心思路是:先按空格把句子拆成单词,对每个单词反转,再重新拼接。但要注意两点:第一,单词之间可能有多个连续空格,不能直接split后简单拼接,否则空格数会变;第二,C++里用getline读取带空格的整行时,要处理好换行符,Java里用nextLine也容易踩坑。
public static String reverseWords(String s) { StringBuilder result = new StringBuilder(); int i = 0; while (i < s.length()) { char c = s.charAt(i); if (c != ' ') { int j = i; while (j < s.length() && s.charAt(j) != ' ') j++; StringBuilder word = new StringBuilder(s.substring(i, j)); result.append(word.reverse()); i = j; } else { result.append(' '); i++; } } return result.toString(); }这段代码的逻辑是:用双指针定位每个单词的起止位置,对单个单词做反转后追加到结果中,空格原样保留。这样做的好处是无论字符串中有多少个连续空格,都不会丢失信息。
字符串题目在笔试里的容错率很低,因为测试用例会故意包含空串、全空格、首尾带空格等特殊情况。我建议你在本地写代码时,至少准备五个测试用例:空字符串、单字符、全空格、正常句子、带多个连续空格的句子。养成这个习惯后,字符串题基本不会翻车。
2.3 动态规划类题:状态定义是核心
A卷的压轴题或者倒数第二题,通常是动态规划。这里的DP不会出得太偏,比较常见的是背包问题的变体、二维路径问题、最长递增子序列等。2020届这次出的是和“任务安排”或“物品选择”相关的背包类变体,核心是考察状态定义和状态转移方程。
以经典的0-1背包为例:有n个物品,每个物品有重量w[i]和价值v[i],背包容量为W,问能装入的最大价值是多少。标准解法是定义dp[i][j]表示前i个物品放入容量为j的背包能获得的最大价值。
状态转移方程:
- 不选第i个物品:dp[i][j] = dp[i-1][j]
- 选第i个物品(前提是j >= w[i]):dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])
def knapsack(W, w, v): n = len(w) dp = [[0] * (W + 1) for _ in range(n + 1)] for i in range(1, n + 1): for j in range(W + 1): dp[i][j] = dp[i - 1][j] if j >= w[i - 1]: dp[i][j] = max(dp[i][j], dp[i - 1][j - w[i - 1]] + v[i - 1]) return dp[n][W]笔试的时候,很多人不是不会写转移方程,而是压根没想到这题要用DP,或者想不出怎么把题目描述抽象成状态。这里分享一个我自己的判断方法:如果题目里出现“最大”“最小”“方案数”这些词,而且直接暴力搜索会指数爆炸,那大概率是DP题。接下来问自己三个问题:状态用什么表示?状态之间怎么转移?初始条件是什么?把这三个问题想清楚,代码就水到渠成了。
还有一种情况是用滚动数组优化空间。在上面的0-1背包中,每一行的状态只依赖上一行,所以可以用一维数组从后往前更新:
def knapsack_optimized(W, w, v): dp = [0] * (W + 1) for i in range(len(w)): for j in range(W, w[i] - 1, -1): dp[j] = max(dp[j], dp[j - w[i]] + v[i]) return dp[W]注意这里的第二层循环必须从后往前,否则会重复选择同一个物品,退化成完全背包。这个细节我面试时问过很多人,能答对的不到一半,笔试时更是有不少人在这一步栽跟头。
2.4 边界与数据规模:你以为写对了,其实没有
在线编程笔试和平时刷题最大的区别,就是存在“数据规模”这个隐形杀手。2020届A卷的编程题虽然整体偏基础,但在数据范围上做了不少文章。
举个例子:题目要求计算某种数组操作的次数,看起来很简单,但如果数组长度n的范围是10^5,那么O(n²)的暴力解法一定超时,必须优化到O(n log n)或O(n)。很多人本地测试小样例没问题,提交后显示超时,就是因为没注意到数据范围。
再比如,如果题目涉及累加和或乘积,要考虑结果是否超出int范围。C++的int是32位,最大约21亿,当n超过10^5且累加和接近n²时,结果大概率溢出。这时候应该直接用long long,避免踩坑。Java也有类似的问题,int溢出后变成负数,会导致判断逻辑出问题。
还有一点是关于取模。如果题目说“结果可能很大,请对10^9+7取模”,那么中间每一步计算都要取模,不能只在最后取一次。尤其是递推类题目,中间结果一旦溢出,后面再怎么取模都是错的。
我的建议是:做每一道题之前,先花30秒扫一眼题目里给出的数据范围,心里大致估算一下自己方案的复杂度能不能过。如果是O(n²)且n是10^5,赶紧想优化方案,别浪费时间写一个必超时的代码。
3. 在线编程平台的实操细节:别让非技术因素拖后腿
3.1 输入输出处理:必须烂熟于心的基本功
在线编程笔试和本地IDE调试最大的区别,就是你需要自己处理标准输入输出。很多同学代码逻辑没问题,结果卡在输入解析上,这实在太冤了。
C++常见场景是用cin读取不定长的输入。比如题目说“第一行一个整数n,第二行n个整数”,可以这样读:
int n; cin >> n; vector<int> arr(n); for (int i = 0; i < n; ++i) { cin >> arr[i]; }如果题目给的输入是“一直读到文件末尾”,则需要用while循环配合cin:
int a, b; while (cin >> a >> b) { // 处理每组输入 }Java这边用Scanner居多,但笔试时数据量大的场景建议用BufferedReader,性能更好:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine().trim()); String[] parts = br.readLine().split(" ");Python则要善用sys.stdin.read()一次性读取再解析,不要用input()一行行读,浪费时间也容易出错:
import sys data = sys.stdin.read().strip().split()还有一个高频坑:有时候输入数据包含了多余的空行或者末尾换行符,如果直接用nextLine或getline,可能读到空字符串导致转换报错。稳妥的做法是每次读一行后先trim掉首尾空白。
这里想多说一句:不要轻视输入输出这道工序。正式笔试的每一分钟都很宝贵,如果连基本的IO都要现场想,时间肯定不够用。建议考前把C++、Java、Python三种语言的常用IO写法各准备一套模板存到脑子或本地笔记里,考试时直接套用。
3.2 答题顺序与时间分配策略
vivo的在线笔试通常总时长在90到120分钟之间,包含客观题和编程题。合理分配时间是拿到高分的关键。我的习惯是:
- 拿到试卷先把所有题目快速浏览一遍,对编程题的难度有个整体判断,先做客观题里自己有把握的部分,不要在一道选择题上纠结超过两分钟。
- 编程题的顺序严格遵循“先易后难”原则。第一道通常是数组/字符串类送分题,20分钟内写完;第二道中等难度,30到40分钟;最后一道如果有思路就写,没思路就先把基础用例的暴力解法写上,能拿部分分就不亏。
- 一定留出最后的10到15分钟统一检查编译和提交。
很多在线笔试平台支持多语言,我建议你选择自己最熟练的语言,不要为了炫技选择不熟悉的语言。比如你平时写Java写得多,突然想在笔试里用Python快速实现DP,结果语法不熟,调试耗掉大量时间,得不偿失。
我备考时的模拟练习方法是:每次模拟笔试都严格限制90分钟,手机静音,不许查资料,模拟真实环境下的紧张感。这样练过几次之后,上了考场节奏感会很稳,不容易慌。
3.3 代码风格与提交规范
在线笔试虽然不要求你写出生产级代码,但代码的可读性会影响面试官对你的印象。尤其是有些笔试平台的代码会留档,面试官在后续面试中可能会翻看你的提交记录。
几个实用建议:变量命名要有意义,不要全是a、b、c;关键逻辑处写简短注释,说明思路;函数块之间用空行隔开;避免深层嵌套,如果if里套if里再套if,说明逻辑可以简化。
还有一点是关于边界条件的处理。在写完主体逻辑后,一定要在代码开头或者关键位置处理空输入、单元素输入等特殊情况。比如数组类的题目,先判断if (arr.empty()) return 0; 这行代码简单到不能再简单,却能避免运行时错误。
有一次我在模拟时写了这样一段代码:遍历数组找最大值,初始化max = INT_MIN,结果数组为空时直接返回INT_MIN,题目最后的预期是0,导致整个测试用例失败。后来我养成习惯,凡是有数组输入,先判断是否为空,再决定怎么初始化变量。进笔试现场之前,这个习惯已经变成了条件反射。
4. 高频失误与调试技巧:从“样例通过”到“完整AC”
4.1 样例通过但提交0分的三个典型原因
在线编程笔试最让人崩溃的场景,就是本地测试明明通过了,提交后却是0分。我自己经历过太多次,总结一下最常见的三个原因:
第一个原因是数组越界或访问了不存在的下标。比如C++里用了vector的size()做索引,但没有注意size()返回的是size_t无符号类型,可能在某些循环条件下产生负数转无符号数的问题。建议把所有涉及索引的循环都写成int且显式判断边界。
第二个原因是多组输入的清空问题。如果平台是多组测试用例连续输入,每处理完一组,相关的计数器、临时数组、标志位都必须重置。我见过一个同学在循环外定义了局部变量,循环内忘记重新初始化,导致第二组用例开始后结果全错。
第三个原因是取模时机不对。前面已经提到过,递推过程中每一步都要取模,而不是最后统一处理。特别是涉及减法的取模,如果中间结果为负,要先加上MOD再取模。
关于第二个原因再展开说一下。有一次我遇到一道题,题目说了多组测试数据,每组数据第一行一个整数n,接下来n行输入,然后输出结果。我写的是:
int T; cin >> T; while (T--) { int n; cin >> n; vector<int> arr(n); for (int i = 0; i < n; ++i) cin >> arr[i]; // 处理逻辑,结果输出 }这个框架本身没问题,问题出在我的处理逻辑里定义了一个std::map,却没有在每组数据之间清空。结果第二组数据来的时候,map里还残留着第一组的结果,直接影响了计数。后来我把map的定义挪到while循环内部,问题就消失了。这是一次记忆很深的实战教训,也让我之后特别重视变量的作用域。
4.2 死循环与运行时错误:如何快速定位
笔试时时间紧张,如果程序运行超时或者崩溃,不要干瞪眼,要有系统性的排查思路。
先说出死循环。常见原因是循环边界写错,比如while (l <= r)写成了while (l < r)导致搜索区间不正确;或者循环体内变量更新遗漏,导致某个变量永远不会接近退出条件。排查办法是检查循环变量的更新语句,确认每一步都在向退出条件靠近。
再说运行时错误,比如空指针、除零、栈溢出。最容易忽略的是除零,尤其是当分母是某个变量而你没有检查它是否为0时。题目如果涉及“求平均值”“求比例”,一定要先判断分母是否为0,否则本地测试数据如果恰好没有0,一到平台就崩溃。
还有栈溢出问题。不少同学在笔试里会不知不觉用递归,比如深度优先遍历二叉树、递归DFS搜索所有排列组合。如果递归深度达到10^5以上,C++默认的栈空间是不够的,很容易爆栈。解决办法是把递归改成显式栈的迭代写法,或者改用动态规划/广度优先搜索。
这里给一个通用调试流程:先加几个中间输出,看看变量的值是否符合预期;如果代码编译不通过,从报错信息的第一行开始排查,不要全篇乱找;如果是运行时崩溃,检查有没有除零、数组越界、空指针这三个经典问题;如果是超时,考虑是不是复杂度太高,能不能用哈希表或前缀和优化。
4.3 常用测试数据的自测方法
一个成熟的做法是在本地准备几个固定模式的测试用例,提交前快速跑一遍。我自己的测试清单包括:
- 普通输入:正常的题目描述示例,验证主逻辑是否正确。
- 边界输入:最小数据规模(n=1,空数组,单字符等),验证边界处理。
- 最坏情况:n取到题目允许的最大值,验证性能和是否溢出。
- 多组输入:连续跑两组,验证每组之间的变量是否清空。
- 特殊数据:全相同元素、全不同元素、升序、降序、包含负数等。
每一道题写完,拿这个清单过一遍,比盲目提交几次再改正效率高得多。尤其是“最坏情况”这一条,可以直接发现O(n²)超时、int溢出这类在普通样例中完全看不见的问题。
这个表你可以在脑子里存一个,也可以写在一张纸上放在旁边。笔试时看到题目,先确认数据范围,再决定算法,最后按清单自测,整个流程下来有条不紊。
5. 备考方向与长期能力建设
5.1 校招笔试前一个月怎么准备
如果离笔试还剩一个月,时间紧任务重,不要漫无目的地刷题,要有针对性地复习。
第一周重点过基础数据结构:数组、链表、栈、队列、哈希表、二叉树。每种数据结构至少能手写一遍基本操作,包括插入、删除、查找、遍历。第二周集中刷高频算法专题:排序、二分查找、双指针、滑动窗口、BFS/DFS、动态规划。第三周做模拟套题,找近几年的知名互联网公司和手机厂商笔试真题,卡时间完整模拟。第四周复盘错题,把不熟的模板重新默写一遍,同时整理自己常用的输入输出模板和代码片段。
这里要特别强调一下手写代码的重要性。你在IDE里写代码时,IDE会自动补全、自动提示,这在笔试时会让人产生依赖。很多在线笔试平台的代码编辑器补全能力很弱,甚至连括号匹配都做得不好。所以平时练习时就应该尽量脱离IDE的自动补全,用纯文本编辑器手写代码,才能提前适应考场环境。
另一个容易忽略的点是目标公司必须“对症下药”。vivo的笔试风格偏向工程、通信、终端设备相关,你刷题时也可以多看看字符串解析、数组操作、存储管理这类贴近硬件的场景题。如果目标公司是纯互联网公司,那侧重可以放在复杂算法和系统设计上。
5.2 长期算法能力怎么培养
如果准备时间比较充裕,或者你还在大二大三,我建议走一条更扎实的路线。
底子是数据结构和算法这门课,不要只背结论,要理解每种数据结构为什么这样设计,时间和空间复杂度是怎么分析出来的。比如哈希表为什么是O(1)的平均查找,它的哈希冲突怎么解决,链表和数组在不同场景下怎么取舍。这些问题不只是考试,面试时也经常被问到。
然后是刻意练习。每天固定刷2到3道题,比周末集中刷20道题效果更好。刷题的时候给自己计时,一道题最多想30分钟,超过时间就看题解,然后隔天再把这道题重新写一遍,做到能独立AC为止。
我自己的经验是:刷题的关键不在量,而在总结归纳。每做完一道题,在笔记里记录它的考点、解法和易错点,定期回顾。比如“双指针”这个技巧,适用于有序数组的两数之和、最长无重复子串、快慢指针找环等很多场景,把它们放在一起对比,比单纯记住一个题解有价值得多。
5.3 笔试之外:从题目看到公司技术风格
最后想聊一个很多人忽略的角度:笔试题目其实透露了公司技术团队的价值取向。
vivo的笔试重点偏向数组、字符串、动态规划和基础数据结构,说明研发团队在实际工作中更看重扎实的编码能力和业务逻辑抽象能力,而不是极致的算法竞赛能力。这也符合手机厂商软件研发的现实——大家写的大多是业务代码,需要能快速把需求抽象成清晰的逻辑结构,而不是整天研究高难度算法。
所以如果你拿到了vivo的笔试机会,答得不好也不要太挫败。认真复盘每一道题,看懂它的考察意图,这本身就是一次宝贵的学习过程。即便这次没过,积累的知识点和方法论对后面的其他公司笔试同样适用。
在线编程笔试是所有研发岗求职者都要过的一道坎。它不像面试那样需要临场表达,也不像简历筛选那样有主观成分,它把所有人的代码能力放到同一个平台上公平比拼。只要你基础扎实、模板熟练、心态稳定,拿到一个不错的成绩并不难。希望这篇拆解能帮你少走弯路,祝你在校招季拿到心仪的offer。