
2022年8月底我卡着最后两天提交了vivo 2023届秋季校招的简历一周后邮箱里躺着一封在线笔试邀请。那场考试给我的印象一直很清晰题量不大总共两道编程题加十几道选择题但两道编程题都套着“手机系统”的业务壳——一道是命令解析一道是存储分配。表面上考的是算法实际做题时一半精力要花在理解场景、抽离模型上。如果你也在准备大厂校招笔试尤其是想冲vivo这种手机厂商的研发岗这篇文章会把这套在线编程的出题逻辑、典型解法、容易丢分的地方一次性讲透。1. vivo 2023届秋招笔试到底在考什么一场 90 分钟的手机系统思维测试1.1 从投递到收到笔试链接一个真实的时间线先说流程。vivo 2023届秋招的在线笔试是网申之后的第一道硬门槛。从身边同学和我自己的经历来看流程一般是在官网或招聘公众号完成简历投递系统通过简历初筛后会在 1 到 2 周内发来笔试邮件邮件里带一个在线测评链接平台用的是常见的牛客网或赛码系统。笔试时长基本都是 90 到 120 分钟题型分成客观题和编程题两部分。客观题覆盖数据结构、操作系统、计算机网络、Java/C 基础这类计算机基础偶尔也会出现一两道安卓开发相关常识题比如 Activity 生命周期、进程通信方式之类。编程题通常是两道一道偏字符串/模拟一道偏动态规划或贪心分值占大头能不能进下一轮基本就看这两道题做得怎么样。这里要提醒一个容易忽略的点在线编程环节的成绩是后续面试排序的重要参考不是随便写了能跑通就行。笔试分数越高面试官手里的“初始印象分”就越高后续手撕代码的压力也会小一些。1.2 从“手机系统”场景反推出来的四个高频考点vivo 的笔试题目不像纯互联网公司那样全是“买卖股票”“最长回文子串”它更爱把题目藏在手机系统常见的场景里。我做完之后复盘发现所有题目基本都能归到下面几类场景方向抽象后的算法考点典型题感设备命令/日志解析字符串处理、状态机、栈、正则解析一条带引号、带参数的命令行手机存储/应用空间分配0-1 背包、贪心、排序给定 ROM 剩余空间选应用使评分最大后台任务/进程调度优先队列、拓扑排序、区间调度多个任务竞争 CPU求最短完成时间通信协议/数据流处理滑动窗口、双指针、前缀和对一段日志流做窗口统计这个分布给我们的复习方向其实很清楚字符串处理、背包类动态规划、贪心排序是必须练熟的尤其是字符串题考的概率最高因为可以很方便地和“指令解析”“日志清洗”挂钩。你不需要成为一个安卓开发专家但最好了解一点设备命令、应用安装、进程调度这些概念的粗糙直觉。这样读题时会更快地还原题目想让你干嘛不会被一堆业务名词绕晕。2. 指令解析题拆解用状态机处理带引号的命令字符串2.1 一道让我印象深刻的命令行解析题我笔试时遇到的第一道编程题是一道“命令解析”题。题目大意是这样的设备调试工具会收到一行命令字符串命令由程序名和若干参数组成参数之间用空格分隔。但参数本身可能被单引号或双引号包裹包裹后的参数内部允许出现空格并且支持用反斜杠转义引号。请将命令解析成程序名和参数列表。示例输入fastboot flash boot boot.img示例输出program: fastboot args: [flash, boot, boot.img]再比如tool --name vivo neo9 --size 512输出应该是program: tool args: [--name, vivo neo9, --size, 512]看到这道题第一反应是不是“用 split( ) 不就行了”真这么写的人AC 率会掉得很惨因为vivo neo9内部那个空格一旦被 split参数就被劈成两半了。这道题真正想考察的是你有没有状态机的思维。2.2 从暴力分割到状态机的完整实现核心思路是遍历字符串的每一个字符用两个布尔变量inSingle和inDouble记录当前是否处于单引号或双引号内部。只有不在任何引号内部时遇到空格/制表符才认为是一个参数的结束如果在引号内部空格就当作普通字符拼进当前参数。一段可行的 Java 代码我贴在这里可以直接跑到在线环境里验证import java.util.*; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); String line sc.nextLine(); ListString tokens parse(line); System.out.println(program: tokens.get(0)); System.out.print(args: [); for (int i 1; i tokens.size(); i) { if (i 1) System.out.print(, ); System.out.print(\ tokens.get(i) \); } System.out.println(]); } static ListString parse(String s) { ListString res new ArrayList(); StringBuilder cur new StringBuilder(); boolean inSingle false; boolean inDouble false; for (int i 0; i s.length(); i) { char c s.charAt(i); if (c \ !inDouble) { inSingle !inSingle; continue; } if (c \ !inSingle) { inDouble !inDouble; continue; } if ((c || c \t) !inSingle !inDouble) { if (cur.length() 0) { res.add(cur.toString()); cur.setLength(0); } continue; } cur.append(c); } if (cur.length() 0) { res.add(cur.toString()); } return res; } }这段代码的核心在于当遇到引号时不是直接丢弃而是切换状态遇到空格时先检查当前状态只有“在外层”的空格才做分割。这个思路几乎适用于所有需要识别上下文边界的解析题。2.3 引号、转义与空串这三个边界让 AC 率掉一半代码只是基础边界才是分水岭。我复盘时总结了三个特别容易挂的坑。第一个坑是引号未闭合。真实输入里可能出现tool abc这种残缺字符串有些在线判题系统不会给这种用例但有些会。处理策略比较稳妥的做法是遍历结束后如果cur非空仍然作为参数加入结果如果引号未闭合不要抛异常按“引号后面的内容全部属于当前参数”来处理也就是我上面代码的默认行为。第二个坑是反斜杠转义。上面代码没有处理\的情况如果题目明确说支持转义就得加一个判断当遇到反斜杠且后面紧跟引号时反斜杠本身不入结果但下一个字符要作为普通字符放进cur。这里有个细节是“反斜杠是否消耗掉”所有解析器都容易在这里翻车建议写题时直接用一两个包含转义的用例验证。第三个坑是连续空格与头尾空格。简单split会在多个连续空格时产生空字符串导致参数列表多出空元素。我的代码通过cur.length() 0判断来跳过连续空格但如果你把“空参数”定义为合法可能需要另做标记。从笔试角度默认忽略空参数更安全。还有一个容易被忽略的问题输出格式。题目说 args 列表要输出成[flash, boot, boot.img]这种格式你输出的逗号后面有没有空格、引号是否和样例一致都算对错。在线判题通常是“特殊判题”或“严格匹配”多一个空格可能整体判错。建议把所有示例输出先原样试一遍再考虑边界。3. 存储分配题拆解从朴素 DP 到滚动数组的优化路线3.1 为什么大厂笔试偏爱“空间分配”这个壳第二道编程题我遇到的是存储空间分配题。题目大意手机剩余空间为total有n个应用待安装每个应用有大小size[i]和安装后的评分rating[i]在不超过剩余空间的前提下选一些应用安装使得总评分最大。问最大评分是多少。这实际上是赤裸裸的 0-1 背包问题。我去网上查了同时期其他同学的反馈发现“空间分配”“容量规划”这类包装在手机厂商笔试里出现频率很高。为什么会偏爱这个壳因为手机系统天然存在 ROM 空间、运存空间、广告位容量这些实际资源题目既能考算法又能暗示你“我们是做硬件和系统出身的公司”一举两得。这种题的难点不在读懂背包模型而在于你能不能快速从“应用、评分”这些业务词里抽象出“物品、体积、价值”三个概念。状态定义一旦确立后面就是体力活。3.2 二维 DP 表格推演动手算一遍才会真正理解我们先用二维 DP 建立直觉。设dp[i][j]表示“只考虑前 i 个应用且占用空间恰好为 j 时能获得的最大评分”。转移方程是dp[i][j] max(dp[i-1][j], dp[i-1][j-size[i]] rating[i])前一项表示不装第 i 个应用后一项表示装第 i 个应用。为了看懂我列了一个具体例子total 104 个应用如下。应用编号大小 size评分 rating123234345458手动推一遍最大评分是选择应用 1、2、4体积23510评分34815。选择应用 1、3、4 体积24511超出容量不行应用 2、3 体积7评分9也不是最优。这就是背包问题典型的“多退少补”权衡。二维 DP 的好处是直观坏处是空间复杂度是O(n * total)。当total达到 1 万、n 达到 5000 时二维数组可能直接 5000 万 int内存约 200MB在线环境大概率爆内存。3.3 一维滚动数组的正确打开方式所以必须压缩成一维。由于dp[i][j]只依赖dp[i-1][j]和dp[i-1][j-size[i]]也就是上一行左边的值我们可以从total到size[i]逆序更新保证更新dp[j]时dp[j-size[i]]还是上一轮的旧值而不是本轮被覆盖后的新值。import java.util.*; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); int total sc.nextInt(); int n sc.nextInt(); int[] size new int[n]; int[] rating new int[n]; for (int i 0; i n; i) size[i] sc.nextInt(); for (int i 0; i n; i) rating[i] sc.nextInt(); int[] dp new int[total 1]; for (int i 0; i n; i) { for (int j total; j size[i]; j--) { dp[j] Math.max(dp[j], dp[j - size[i]] rating[i]); } } int ans 0; for (int v : dp) ans Math.max(ans, v); System.out.println(ans); } }这里有一个绝大多数新手都会忽略的点最后答案不一定是dp[total]而是dp[0..total]的最大值。因为“不超过剩余空间”并不要求恰好装满可能存在更优解占用空间没到total。我考试时一开始直接输出dp[total]对照样例过了但隐藏用例里很可能有大量容量有剩余的情况差点踩坑。3.4 如果题目要求输出“选哪些应用”用回溯还原路径有些变形题会多问一句输出被选中的应用编号列表。这时一维 DP 就不够用了因为滚动数组覆盖了历史状态无法还原路径。正确做法是保留二维 DP 数组做完后从in到i1倒推如果dp[i][j] ! dp[i-1][j]说明第 i 个应用被选入了记录编号然后j - size[i-1]。ListInteger chosen new ArrayList(); int j total; for (int i n; i 1; i--) { if (dp[i][j] ! dp[i - 1][j]) { chosen.add(i); j - size[i - 1]; } }这个技巧在“方案输出型”背包题里几乎是标配建议整理成自己的模板考场上直接用。笔试时间宝贵临时推导容易出错。4. 在线编程系统里不写进题干的“隐藏分”4.1 题量分布与答题顺序编程题永远优先vivo 的在线笔试整体题量不大但客观题如果磨蹭起来会严重挤压编程题时间。我的个人策略是先花 30 秒扫一眼编程题如果脑子里有思路立刻先做编程题如果完全没有思路先做客观题让潜意识在后台“跑”一会儿回头再看编程题。为什么要这样安排因为客观题一道可能就 1-2 分编程题一道动辄 30-50 分。即便你客观题全部猜对也顶不上编程题一半的分值。把编程题放在后面、最后因为时间不够没提交是笔试里最亏的输法。编程题内部也有顺序讲究。一般第一道字符串题偏简单第二道 DP 或贪心偏难。先把能 AC 的题完整拿到分再去啃难题而不是从第一道开始闷头写到底。4.2 自测用例设计你手里的样例不是所有样例在线编程最隐蔽的坑就是“样例过了但提交只有 0 分”或者只有部分分。因为样例只是给一个最基本的正确性参考真正的评测用例里有大量边界空字符串、全空格、超大 n、负数、溢出、重复元素、字符串长度 1 等等。我每次写完代码后会做一组“自测清单”最小输入n0 或 total0 能不能正常输出全边界输入字符串全是空格、应用体积全部大于总容量最大规模n 取到题目上限检查是否会超时或爆内存重复输入多个相同体积的应用会不会因为状态覆盖导致错误输出格式有没有多余空格、换行、调试打印。特别是最后一条很多人调试时会在代码里留System.out.println(debug...)提交前忘记删直接导致格式错误甚至判 0 分。这种错误在牛客和赛码平台都遇到过不是个案。4.3 输出格式、静态查重与代码规范在线编程系统除了跑用例后台还会做相似度检测和代码风格审查。vivo 这类大厂对查重非常严格一旦判定为抄袭后果很严重。所以不要去背网上现成的题解原文而是理解思路后用自己的写法重新实现变量名、注释、代码结构都保持自己的习惯。代码规范同样重要。虽然机改不会因为你变量命名不好就扣分但有些题目会进入后续的人工复看环节面试官会翻你的提交记录。清晰的分段、必要的注释、合理的函数拆分这些在面试官眼里都是加分项。我见过一个同学同一道题因为函数拆得好、逻辑清晰面试时被直接跳过代码追问环节。5. 秋招倒计时四周一份可落地的笔试备考计划5.1 按考点分周推进的刷题节奏如果你的时间只剩一个月不要东一榔头西一棒子地刷。建议按下面的节奏推进每周末做一次成套模拟第一周基础数据结构扫盲。数组、链表、栈、队列、哈希表、二叉树保证每种结构至少能手写出来并知道典型操作的时间复杂度。这一周不用刷难题把 1-2 道基础题练熟即可。第二周火力集中在动态规划和贪心。0-1 背包、完全背包、最长上升子序列、最大子数组和、区间调度、跳跃游戏每类刷 5 题左右。动态规划的重点不是看题解而是自己动手填一遍状态转移表。第三周字符串与模拟题专项。字符串分割、引号解析、表达式求值、括号匹配、大数相加等。这类题在 vivo 笔试里出现概率最高刷的时候要养成“先想边界再写循环”的习惯。第四周成套真题模拟。找两三套大厂校招笔试真题按正式考试时间在一个安静环境下完完整整做一遍包括客观题。重点练的不是会不会做而是时间分配和心态。5.2 笔试现场的时间止损策略很多人在考场上的问题不是不会做而是死磕一道题导致后面全崩。我给自己定的规矩是一道编程题如果 20 分钟没有形成清晰思路立刻停手先写一个暴力解法拿到部分分再回头看能不能优化。为什么要先写暴力解因为在线判题系统通常是按照通过的测试点给分。即使时间复杂度不达标能过掉一部分小规模用例也能拿 10 到 20 分。空着是 0 分暴力拿一半分优化后说不定能全过。这个账一定要算清楚。另外如果输入规模明确在 10^5 量级而你只能写出 O(n^2) 的解法哪怕样例过了也别抱侥幸正常的评测用例一定会给你加数据量。这时候别浪费时间优化常数直接换思路。5.3 我记录过的 10 个高频失误与应对我在准备和实战中踩过不少坑也帮同学看过不少代码下面这 10 个是最常见的每一个都能在瞬间把你从 AC 拉到 0 分序号失误类型具体场景应对办法1没有 import 完整包用了Arrays.sort但没import java.util.*提前写熟标准 import 段2变量范围用错int存 10^9 以上累加导致溢出数据范围大时直接上long3数组越界循环里i1没有限界检查所有数组访问下标4死循环while条件更新位置错误每个循环体里确认循环变量会变5栈溢出递归深度超过线程栈数据大时改成迭代或显式栈6输出多余调试信息判题时输出 log提交前全局搜索 print/debug7没处理空输入nextLine()读到空串读入后先判空再解析8动态规划遍历方向错误一维背包正序更新记住01 背包逆序完全背包正序9初始化错误dp数组填 0 还是填-INF没想清先明确状态定义再决定初始值10读题漏条件没看到“若评分相同则输出编号最小”把题目里的限制条件先圈出来这 10 条不是理论是我周围几十个同学真实提交记录里的高频问题。每次模拟笔试后我都会对照这个清单反省一次效果比盲目多刷题更明显。最后再分享一个我自己的习惯笔试前一个小时不要再看新题而是把常用模板快速默写一遍包括背包模板、二分模板、并查集模板、字符串状态机模板。裸写模板不是为了考场上原样抄而是让自己进入“做题状态”顺便激活手部肌肉记忆。vivo 2023届秋季校招在线编程那场我就是用这个办法在开始前快速找回手感最后两道题都顺利跑通。希望这份经验也能帮你在下一场笔试里少踩几个坑。