news 2026/9/1 8:22:53

vivo秋招软件岗笔试复盘:题型考点与编程题实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
vivo秋招软件岗笔试复盘:题型考点与编程题实战解析

2023年vivo秋招软件岗第一批笔试,我在交卷前5分钟把一道动态规划改成了贪心,没想到居然过了。这里把整场笔试的题型、考点、踩坑点原原本本复盘一遍,给后面准备手机厂商软件岗的同学一些参考。

先说结论:vivo笔试整体难度在主流厂商里算中档偏上,选择题覆盖面很广但不算偏,重点在计算机网络、操作系统、C++/Java语言特性和数据库;编程题三道,没有纯模板题,每一道都需要一定的思维转换,而不是背板子就能秒。

1. 整体情况与题型结构分析

1.1 考试形式与时间分配

vivo秋招软件岗笔试用的是牛客网平台,限时90分钟,总共大概28道题,题型分三块:单选题(约15道)、多选题(约7道)、编程题(3道)。总分100分,其中选择题占50分,编程题占50分,分值基本对半开,也就是说编程题和选择题同样重要,不存在“选择题随便答,编程题写出来就行”的说法。

时间分配上,我建议选择题控制在40分钟以内,给三道编程题留至少50分钟。但实际操作下来,单选题里藏着不少需要动笔算的题(后面会详细说),一不留神就会超时,所以做选择题时一定要有“取舍感”:想超过两分钟还没思路的题,先标记跳过,不要恋战。我这次就是在一道C++内存对齐的多选题上卡了五分钟,导致后面编程题时间被压缩,第二道题差点没写完。

牛客网笔试页面支持本地IDE写代码然后粘贴提交,但考试过程中不能切换浏览器标签页或使用本地编译器,全程监测浏览器行为。所以平时练习就尽量习惯在网页编辑器里写代码,不要依赖IDE的代码提示,否则笔试时会很不适应。

1.2 考察范围权重分析

从我个人回忆和同批笔试群里讨论的情况来看,vivo软件岗笔试的知识点覆盖面大致呈现这样的权重分布:

模块占比估算高频考点
计算机网络20%-25%TCP三次握手、拥塞控制、HTTP状态码、DNS解析
操作系统15%-20%进程线程区别、死锁条件、页面置换算法、进程调度
C++/Java语言20%-25%虚函数、内存对齐、智能指针、JVM内存结构、并发包
数据库10%-15%索引结构、事务隔离级别、B+树、SQL语法
智力题/数学5%-10%概率计算、排列组合、逻辑推理
数据结构与算法10%左右复杂度和代码题相关基础概念

这个分布基本代表了手机厂商软件岗的技术考察偏好:操作系统和计算机网络是重点,因为它们直接关系到底层系统优化和通信协议理解;语言特性侧重内存管理相关知识点,这和C/C++开发方向的岗位需求强相关。

一个值得注意的细节是,vivo的笔试题目明显比互联网大厂更“扎实”,没有那种靠猜的脑筋急转弯题,大部分选择题都是实打实的课本知识结合实战场景。比如TCP拥塞控制那道题,选项不是简单背“慢开始、拥塞避免、快重传、快恢复”的顺序,而是给出一个具体的cwnd变化序列,让判断处于哪个阶段,对知识点的理解深度要求更高。

2. 选择题核心考点详解与避坑建议

2.1 计算机网络:协议细节是分水岭

vivo笔试的计算机网络题,难度比很多互联网公司高一个档次。我这里印象最深的一道题是TCP拥塞控制的综合分析:题目描述一个TCP连接从建立开始,ssthresh设为16,当cwnd增长到多少时进入拥塞避免阶段?发生超时后,ssthresh和cwnd分别变成多少?

这题光背“慢开始指数增长,拥塞避免线性增长”是不够的,你得知道具体是cwnd超过ssthresh后才进入拥塞避免,而且必须注意慢开始阶段cwnd从1开始,每轮RTT翻倍,但翻倍是在收到全部ACK之后,不是发送之后立即翻倍。我第一次做这种题也经常算错,后来总结出一个口诀:计数,翻倍,再判断。一轮传输结束后,先把cwnd的值与ssthresh比较,决定下一轮按什么算法增长。

HTTP状态码那道题难度不大,问的是301、302、304、404分别代表什么,以及浏览器在什么场景下会缓存重定向结果。这题的坑在于304和307容易混淆,304表示内容未修改,服务器告诉浏览器可以用缓存;307表示临时重定向,且保持请求方法不变。我记得当时还出了一道DNS解析流程的选择,问的是浏览器访问一个域名时,DNS查询的先后顺序是“浏览器缓存-系统缓存-路由器缓存-本地DNS服务器”。考点不偏,但需要对整个链路有清晰认知。

2.2 操作系统:死锁和页面置换是常客

操作系统板块,vivo对死锁理论的考察很有意思。题目没有直接问“死锁的四个必要条件是什么”,而是给出一个具体的资源分配场景:两个进程各自持有一个资源,又同时请求对方持有的资源,问这属于死锁的哪个必要条件中的哪个场景。这就是典型的“知识点变形”,如果你只是背了四个条件的名字,不知道“保持并等待”“不可剥夺”“循环等待”如何在具体场景中体现,就很容易选错。

页面置换算法也考了一道LRU,给了一个访问序列,问物理块为4时的缺页次数。这类题没有捷径,必须动手画表,一个访问一个访问地推。我建议平时练习时就养成用表格推演的习惯,这样考场上能快速准确算出结果。计算完建议把结果代入检查一遍,因为一位数字之差就会导致完全不同的答案。

进程和线程的辨析题比较基础,考的是线程共享哪些资源、独立哪些资源。这题的经典陷阱是“线程共享进程的地址空间,但有自己的栈和寄存器”,部分选项会故意把栈说成共享的,看题要细。

2.3 语言特性:内存细节决定成败

vivo笔试混考了C++和Java的内容,这也符合软件岗统一笔试的风格,考生需要按自己擅长的语言方向作答,但对方语言的基础也不能完全不懂。

C++部分,我遇到的内存对齐计算题值得单独说。题目给了一个结构体:

struct Test { char a; // 1字节 int b; // 4字节 short c; // 2字节 };

问 sizeof(Test) 是多少,并给出各成员偏移量。

这题的答案是12,不是简单的1+4+2=7。计算过程是:char a占偏移0;int b需要4字节对齐,所以b从偏移4开始,中间空出3个字节;short c需要2字节对齐,从偏移8开始,占2字节到偏移9;结构体总大小需要是最大对齐数(这里是4)的整数倍,所以是12。

我当时在这道题上浪费了不少时间,因为选项里还有sizeof在默认对齐和#pragma pack(1)下的两种结果。建议准备笔试时把“T内存对齐、pragma pack、offsetof宏”这几个知识点串起来复习。

C++虚函数表的题也考了一道,问的是“一个类有多个虚函数,请问该类的对象内存中存储的虚函数表指针有几个”。正确答案是1个,因为同一张虚函数表里按声明顺序排列所有虚函数地址,不是每个虚函数一个指针。这和Java的虚方法分派思路类似,很多同学容易踩坑。

Java板块的题集中在JVM内存结构和并发包。JVM那道题问的是“JDK8中,以下哪个区域在OOM异常时抛出 OutOfMemoryError: Metaspace”,这个其实就是本地内存区域里的元空间。Java并发题考的是ConcurrentHashMap在JDK7和JDK8中实现的关键差异:JDK7用分段锁(Segment),JDK8用CAS+synchronized锁Node头节点。这类题靠突击无效,需要平常积累。

2.4 数据库与逻辑题

数据库题相对友好,B+树的特征、事务隔离级别和脏读/幻读的关系是必考内容。我遇到的是InnoDB存储引擎里RR(可重复读)隔离级别能否解决幻读的问题,答:通过MVCC+间隙锁(Gap Lock)可以在大多数场景下避免幻读,但在某些边界条件下仍可能出现幻读,比如条件列没走索引时,间隙锁退化成表锁,还是会有并发风险。这个细节如果只是背“RR隔离级别解决了幻读”,这道题就挂了。

逻辑题只考了一道概率:两个人轮流抛硬币,先抛到正面的人获胜,问先手获胜的概率。答案是2/3,很多人写的是1/2,忽略了“第一轮先手没有抛出正面,后手抛出正面”这类多轮情况。求解思路是:设先手获胜概率为P,第一轮赢的概率是1/2;如果第一轮双方都没赢(概率1/4),则回到初始状态,所以P=1/2+1/4×P,解得P=2/3。

3. 编程题实战场:三道题的完整解题复盘

3.1 第一题:滑动窗口变种,简单但容易踩坑

第一道编程题难度不大,题目大意是:给定一个整数数组和一个目标值k,输出数组中所有长度为k的连续子数组的平均值中的最大值。

这题的核心考点是滑动窗口,而不是每个窗口单独求和。第一次做这类题的同学容易写出暴力解法:每到一个位置,内层循环遍历k个数求和,时间复杂度O(n×k)。当数组长度到达10^5级别、k也很大时,直接就超时了。

正确解法是维护一个窗口和,每次窗口右移一格,减掉离开窗口的元素,加上新进入窗口的元素,把每次窗口的均值记录下来取最大:

#include <vector> #include <algorithm> using namespace std; double maxAverage(vector<int>& nums, int k) { int n = nums.size(); if (n < k) return 0.0; long long sum = 0; for (int i = 0; i < k; i++) sum += nums[i]; long long maxSum = sum; for (int i = k; i < n; i++) { sum += nums[i] - nums[i - k]; maxSum = max(maxSum, sum); } return (double)maxSum / k; }

这里有个小坑是必须用long long保存窗口和,因为元素可能很大,int溢出会导致答案错误。vivo的笔试平台用的是牛客自带的判题系统,只给错误样例的几个简单用例,不会跑大数据集,所以这种情况很可能交上去显示AC,但实际上没有AC,真实情况会因为你没写long long 导致部分测试用例过不去。我在代码里直接用long long,规避了这个风险。

第二坑是double的精度问题,求均值时直接除k就行,不要用“加上每个元素除以k的值”这种公式,浮点数累加误差会变大。

3.2 第二题:贪心场景题,读完题要想清楚模型

第二题是一道游戏场景题,大意是:有一个技能可以造成范围伤害,给定怪物血量数组和每个怪物的位置坐标,每次可以选择一个位置释放技能,对该位置左右范围L内的所有怪物造成一次伤害,问最少需要释放多少次技能才能杀死所有怪物。

这道题实际上是一个区间覆盖问题,但套了一层游戏皮。很多人读完第一反应是动态规划或二分答案,其实贪心就能解决:对于最左边还没被覆盖的怪物,最优策略一定是在“能覆盖到这个怪物的前提下,技能释放位置尽量靠右”,这样能覆盖到更多右边的怪物。

#include <vector> #include <algorithm> using namespace std; int minSkills(vector<int>& positions, vector<int>& hp, int L) { int n = positions.size(); vector<pair<int, int>> monsters; for (int i = 0; i < n; i++) { monsters.push_back({positions[i], hp[i]}); } sort(monsters.begin(), monsters.end()); int res = 0; vector<int> cur; for (int i = 0; i < n; ) { if (cur[i] <= 0) { i++; continue; } // 技能释放位置的右边界 int rightBound = monsters[i].first + L; int idx = i; while (idx < n && monsters[idx].first <= rightBound) { idx++; } idx--; // 对 [i, idx] 范围内的怪物造成一次伤害 for (int j = i; j <= idx; j++) { hp[j] -= 1; } res++; } return res; }

这个暴力写法时间复杂度过高,正确的做法是记录当前技能覆盖到的最右位置,用一个变量维护已经造成的累计伤害,每个怪物血量减去累计伤害后若仍有剩余,则新增技能并把覆盖边界右移。

这道题让我最难受的地方是思路转换。当时我先写了一个区间DP的解法,写了半天发现状态转移特别复杂,后来冷静下来才发现是贪心。写代码前如果能在草稿纸上做几个小样例推演,比如只有三个怪物、L很小的边界情况,很快就能看出贪心策略是对的,代码量也会小很多。

3.3 第三题:树形DP与哈希结合,区分度最高

第三题是三道题里最难的,也是整个笔试区分度最高的一道,题目大意是:给定一棵树,每个节点有一个颜色值,问从根节点到每个子树中,出现次数最多的颜色是哪种,若多个颜色出现次数相同,输出颜色值最小的那个。

看到这题,我的第一反应是树上启发式合并(DSU on Tree),因为它完美匹配“统计子树中出现次数最多的颜色”这个场景。DSU on Tree的核心思想可以这样理解:统计一个节点的子树信息时,先暴力处理所有轻儿子的子树,再处理重儿子,保留重儿子的信息不删除,这样每次重儿子的结果可以直接复用,把复杂度从O(n²)降为O(n log n)。

简单来说分三步:

第一步,求出每个节点的重儿子。重儿子就是子树节点数最多的那个子节点,类似树链剖分里的定义。

第二步,DFS遍历时,对每个节点先递归处理所有轻儿子,处理完一个轻儿子就把它的贡献从计数数组中删除,然后递归处理重儿子,重儿子的贡献保留在数组里。

第三步,把当前节点自己和所有轻子树的贡献加入计数数组,然后更新答案。

核心代码结构大概是:

void dfs(int u, int fa, bool keep) { // 先处理轻儿子 for (int v : children[u]) { if (v == fa || v == heavySon[u]) continue; dfs(v, u, false); } // 再处理重儿子,保留状态 if (heavySon[u] != -1) { dfs(heavySon[u], u, true); } // 加入轻儿子和当前节点的贡献 add(u, fa, false); cnt[color[u]]++; if (cnt[color[u]] > maxCnt || (cnt[color[u]] == maxCnt && color[u] < ansColor)) { maxCnt = cnt[color[u]]; ansColor = color[u]; } // 加回所有轻子树贡献 for (int v : children[u]) { if (v == fa || v == heavySon[u]) continue; addSubtree(v, u); } ans[u] = ansColor; // 如果 keep 为 false,清空贡献 if (!keep) { removeSubtree(u, fa); } }

这段只是结构示意,实际代码里add和remove函数必须逐节点遍历子树更新数组。我就是把“先加轻儿子,再加重儿子贡献”这个顺序搞反了,导致统计结果出现偏差,调试了二十分钟才定位到问题。所以第二题做完后,我看到时间只剩不到二十分钟,心里是很慌的,这道题完全是靠平时背过的模板撑下来的。

后来笔试群里不少同学反馈这道题连题面都没读懂,或者只写了暴力遍历每个子树统计O(n²)的解法拿到部分分,可见vivo把区分题放在最后一题,就是用来卡大厂的“编程题熟练工”的。

3.4 编程题的整体策略总结

结合三道编程题,我的建议是:第一题一般是滑动窗口、双指针或者简单模拟,10分钟内必须AC;第二题是贪心或二分答案,20分钟内必须AC;第三题是树、图或动态规划综合题,给到20到25分钟,尝试用最熟练的模板解决。

如果第三题完全没思路,把暴力解法写上去也能拿分。vivo的编程题是按测试用例百分比给分的,部分AC比完全空着强很多。但这个“部分分”平时刷题很难练,因为LeetCode等平台不允许你提交非最优解。

4. 备考策略与实战经验总结

4.1 时间分配与刷题方向

回忆完整场笔试,我认为vivo软件岗笔试对候选人的要求集中在三个方面:基础扎实(选择题不丢分)、模板熟练(编程题前两道快速AC)、思维深度(第三题有思路)。

选择题部分,建议重点复习:

  • 计算机网络:TCP拥塞控制、TCP/UDP区别、HTTP/HTTPS通信过程、DNS
  • 操作系统:死锁、页面置换、进程调度、内存管理、线程模型
  • 语言特性:C++虚函数和内存布局、智能指针、Java JVM和集合源码
  • 数据库:索引原理、事务隔离级别、SQL优化

编程题方面,vivo比较喜欢考滑动窗口、贪心、前缀和、树结构遍历、图的最短路径这五类。刷题时不要盲目追求难题,把常见模板吃透更重要。我个人的经验是:LeetCode Hot 100里的中等题刷两遍,再加一些树形DP和区间DP的专项训练,应付这类厂商笔试完全够用。

4.2 我在考场上的几个失误

复盘过程中,我发现了自己的三个失误,写出来给大家提个醒。

第一个失误是多选题中C++内存对齐那道题耗时过长,导致编程题时间被压缩。多选题分值高,如果选错一个选项就全扣,所以遇到需要计算的题,如果估算超过三分钟,果断先标记,做完其他题再回头算。千万别在一道2分的题上丢掉后面20分的编程题时间。

第二个失误是编程题第二题写完后没有充分测试边界情况。好在这道题我用的贪心思路本身是对的,但如果有大量怪物血量相同的情况,代码里累计伤害的逻辑会覆盖不到,导致部分用例超时。以后写题还是要留两分钟在草稿纸上构造几个边界用例,比如“所有怪物血量都是1”“k等于数组长度”“数组只有一个元素”等,再交卷。

第三个失误是第三道树形DP没有在写代码前确认“多个颜色出现次数相同输出颜色值最小”这个比较逻辑。如果当时想清楚了这个细节,代码里的比较条件是可以抽出成一个小函数,就不会因为粗心写错。这个如果在核心代码段的if条件里出错,调试起来极其浪费时间。

4.3 关于笔试的几句真心话

面完vivo这批笔试之后,我最大的一个感受是:手机厂商软件岗的笔试风格比互联网大厂更“学院派”,对基本功的考察非常扎实,不像有些公司刷一套题就能过,而是需要长期积累。这种笔试形式决定了突击复习效果有限,真正能拉开差距的,是操作系统、计算机网络这些计算机基础课程学得扎不扎实,以及日常刷题时有没有形成自己的模板库。

备战的最后一周,与其刷一百道新题,不如把之前做过的高频模板题重新敲一遍。我考前就把滑动窗口、二分答案、DSU on Tree、并查集、快速幂这些模板手敲了三遍,敲到不用思考就能写出来。事实证明,考场上的紧张状态下,只有形成肌肉记忆的东西才写得出来。

如果你马上要参加vivo或同类型公司的笔试,再多说一个细节:牛客网写代码时,读入一定要用快速IO,C++就用scanf/printf或关闭同步的cin/cout,不要直接cin/cout裸奔,数据量一大会超时。这种低级失误最可惜,一定要提前规避。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 8:22:26

NASTool v2部署指南:从容器概念到媒体库自动化

很多人第一次听说 NASTool&#xff0c;是在一个 NAS 折腾群里&#xff1a;有人晒出自动整理好的海报墙&#xff0c;新剧一更新就自动下载、自动改名、自动进库。于是你也去装了一个 NASTool v2&#xff0c;结果打开页面&#xff0c;看到满屏的“索引器”“下载器”“媒体服务器…

作者头像 李华
网站建设 2026/9/1 8:16:40

从H桥到FOC:电机驱动与控制全链路工程实践指南

在机器人、自动化、无人机和智能硬件领域&#xff0c;电机驱动与控制是连接数字指令与物理动作的核心桥梁。无论是让机械臂精准抓取&#xff0c;还是让无人机稳定悬停&#xff0c;其背后都离不开对电机转矩、转速和位置的精确控制。然而&#xff0c;从原理图上的一个H桥电路&am…

作者头像 李华
网站建设 2026/9/1 8:14:42

理解日元汇率:从利差、避险到政策干预的完整分析框架

上周一个朋友问我&#xff0c;日元汇率还会不会继续贬值。他手里有一笔日元&#xff0c;既怕换早了&#xff0c;又怕换晚了。这个问题的难处在于&#xff0c;它不是一个“看多看空”的问题&#xff0c;而是一个结构问题。如果把日元汇率当成一个数字去猜&#xff0c;你会被每一…

作者头像 李华
网站建设 2026/9/1 8:10:36

【linux基础操作-2】

history:查看历史指令默认1000 vim /etc/profile用/HIS查找 1000为可查询历史命令长度 reboot 退出后重启 账户管理&#xff1a; cat /etc/passwd 查看用户账号 由7个字段组成&#xff0c;字段之间用“:”分隔&#xff0c;意义&#xff1a;账号名:密码:UID:GID:个人资料:主目录…

作者头像 李华
网站建设 2026/9/1 8:03:46

技嘉主板QFlash刷BIOS完整流程:从解压到验证避坑指南

简介&#xff1a;面向物联网模块开发与维护人员的移远EC20系列固件升级工具包&#xff0c;收录QFlash V4.17主程序及配套组件&#xff0c;解决EC20模块固件下载、烧录与故障恢复等日常维护问题。包体共280个文件、约57.78MB&#xff0c;以dll动态库、exe可执行程序、bin/cfg/co…

作者头像 李华
网站建设 2026/9/1 8:00:18

Matlab实现SVM回归与分类预测:从数据预处理到参数调优实战

简介&#xff1a;面向机器学习初学者的Matlab SVM预测实践包&#xff0c;围绕支持向量机在分类与回归中的应用展开&#xff0c;包含SVM训练、核函数实现、SVR回归模拟及测试数据。支持向量机通过最大化分类间隔提升泛化能力&#xff0c;核技巧可将低维数据映射到高维空间以处理…

作者头像 李华