
第一次坐进“华为OD机考双机位C卷”的考场时我花了不少时间调整手机支架。正面摄像头对着人脸侧后方摄像头盯着电脑屏幕两个画面同时开着桌面上的题名叫“开心消消乐”——我很确定这不是让我放松的休闲游戏而是一道需要用 Java 完成的算法题。这道题的题面很朴素一个 m 行 n 列的棋盘格子里只有 0 和 11 表示有图标0 表示空位。点击一个 1这个格子以及上下左右相连的所有 1 会一起消除问最少点几次能把整个棋盘清扫干净。懂点算法的人一眼就能看出来这题本质是连通块计数跟 LeetCode 的岛屿问题同源。但在双机位监考、网页编辑器、没有 IDE 补全的环境里从读数据到提交全绿中间还是有不少暗坑。这篇文章就把我的完整解法、踩坑过程以及 Java 机考现场的一些实操经验都写出来给同样在备考的 Java 方向朋友做个参考。1. 双机位监考怎么架、C卷题目怎么抽进入考场前的真实环境1.1 双机位的典型摆法双机位不是打开两个页面就算数它是两路实时画面同时监控。大多数场次的要求是这样的主机位用电脑自带摄像头正对着你的面部画面里要能看到脸、双手、桌面大部分区域第二机位用手机或者另一台摄像头放在你侧后方大概 45 度角的位置完整的照到电脑屏幕和手部动作。我当时的布置是主摄像头放在显示器正中上方稍微俯视确保键盘和胸前位置可见。手机用一个落地支架架在左后方画面稍微拉远一点既能拍到正面屏幕也能拍到我的侧身动作。这里有几个容易忽略的细节手机最好设置成飞行模式并连接 Wi-Fi避免考试中途来电打断画面。桌面要清理干净水杯、手机、纸质资料这类东西提前拿开监考端如果看到可疑物品会要求你展示桌面。光线不要背光正对窗户会让人脸发黑监考端一旦看不清面部可能要求你换位置。这些不是官方文档里写得特别细的内容但实际开考前十分钟如果调试不好非常影响心态。我当时光调整第二机位角度就花了五分钟等真正开始做题的时候第一题已经在屏幕上了。1.2 C卷的构成与成绩结构华为OD机考常见的是 A/B/C 卷随机抽题C卷不是比 A/B 卷更难而是不同的题目集合。每场考试的卷子从题库里随机抽取本质是防泄题和防作弊。常见的题量是三题总分 400 分有 100 100 200 的结构也有 100 200 200 的结构具体要看当时邮件通知。时间通常是 120 到 180 分钟之间不同批次可能不一样一定以考试邮件为准。网页端编辑器只提供基础的代码高亮和运行功能没有自动补全也没有本地 IDE 那么顺手。Java 的话主类名一般固定为 Main不要写 package 声明提交代码时只提交 .java 文件内容。很多人关注“华为OD好进吗”我的体感是机考这一关反而是最可控的。题目难度分布通常是一道简单、一道中等、一道偏难总分过线就能进入下一轮。真正拉开差距的往往是第二题第三题的时间分配而不是第一题会不会。“开心消消乐”这道题在 C 卷里定位就是送分题但也正因为是送分题读题不仔细、输入没处理对照样会拿不到满分。1.3 看到“开心消消乐”这个题名的第一反应说实话第一眼看到“开心消消乐”我愣了一下以为是要实现一个消除游戏的主流程担心要处理下落、合并、随机生成这些复杂逻辑。把题面完整读完之后才放心它说的就是点击消除消除后棋盘不会发生塌陷或下落问最少点击次数。这时候我对这道题的判断就很明确了输入m 行 n 列格子值 0 或 1。操作点击任意一个 1这个 1 与上下左右相连的所有 1 同时变成 0。目标把棋盘上所有 1 清空求最小点击次数。这就是标准的二维矩阵连通块数量统计。一个连通块里点任意一个格子就能消掉整个块不同连通块之间互相独立所以最少点击次数正好等于 1 的连通块总数。思路清楚之后剩下的就是 Java 代码怎么写更稳。2. “开心消消乐”题面还原点一下消一片本质就是连通块计数2.1 我遇到的这版题面还原为了让后面代码部分更好理解我根据自己的记忆还原一下题面不同批次的措辞可能略有差异但算法模型基本一致有一个 m 行 n 列的棋盘格子里可能有图标也可能为空。图标用 1 表示空位用 0 表示。每次操作时玩家点击一个还存在的图标这个图标以及与其上下左右相邻的图标会被一起消除被消除的位置变成空位。请问最少需要点击多少次才能把棋盘上所有图标全部消除示例输入3 3 1 0 1 1 1 0 0 0 1棋盘里三个 1 的连通块分别是左上角与中间左侧连成一块右上角单独一块右下角单独一块所以输出是 3。这类题偶尔会改一点包装比如有些版本里棋盘元素是字符“A”“B”而不是 0/1但做法完全一样。也有一些变体真的会加入重力下落或者自动连锁消除的机制那种题的算法就完全不同了。所以读题阶段最重要的一件事就是确认“点击一次到底消除哪些格子消除后棋盘动不动”。判断错了后面全盘皆输。2.2 把“游戏规则”翻译成算法模型“最小点击次数”之所以等于连通块数量逻辑上很好推在一个连通块内部任意两个 1 之间都存在一条由相邻 1 组成的路径。你点击块内任意一个格子整条路径上的 1 全都被蔓延消除最终整个块变 0。想要再消别的块必须点块外的格子。所以每个连通块至少需要点一次并且每点一次整块就没了。既然每个块只需要一次那最少次数就是块的总数。有了这个结论问题退化成统计二维矩阵中所有值为 1 的格子组成的连通块数量其中相邻关系只包含上下左右四个方向。遍历顺序很简单从上到下、从左到右扫一遍。碰到一个 1就把计数器加一然后以这个点为起点把所有和它相连的 1 都改成 0避免后面重复计数。等整个矩阵扫完计数器里就是答案。2.3 用一个手工算例验证思路拿一个 4 × 4 的例子说明1 1 0 0 0 1 0 1 1 0 0 1 0 1 1 1按连通块划分左上角一片包含 (0,0)、(0,1)、(1,1)共 3 格。右上角单独一个(1,3)。左下角一片(2,0) 与 (3,1) 并不是上下左右相邻仔细看(2,0) 是单独的(3,1)、(3,2)、(3,3) 是一块(2,3) 也在这一块里因为它和 (3,3) 上下相邻。第 (1,3) 和 (2,3) 是上下相邻的所以它们属于同一块要和右下大块合并。整理后一共是 3 个连通块输出应该是 3。如果扫描时不把已访问格子改成 0右下这一大片会在外层循环里被重复计数很多次这也是新手最常犯的错误。3. Java 两个解法递归 DFS 与 BFS 队列完整代码可以直接抄3.1 机考答题的 Java 主结构机考环境里 Java 通常只能用标准库主类名固定是 Main不能带 package。我习惯用一个干净的类结构import java.util.*; public class Main { public static void main(String[] args) { // 读入处理 // 遍历棋盘并统计连通块 // 输出结果 } }读入方式有两种选择Scanner 简单直观适合快速写第一版但数据量大的时候Scanner 逐行解析慢而且在处理混合输入时容易因为 nextInt 和 nextLine 混用而出 bug。我更推荐 BufferedReader 配合 StringTokenizer后面第 4 章会专门讲这个问题。3.2 递归 DFS 写法DFS 的思路是收到一个起点后立刻把这个点改成 0再向四个方向递归。这样既起到了访问标记的作用也完成了消除操作。代码非常简短import java.util.Scanner; public class Main { static int[][] dirs {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; public static void main(String[] args) { Scanner sc new Scanner(System.in); int m sc.nextInt(); int n sc.nextInt(); int[][] grid new int[m][n]; for (int i 0; i m; i) { for (int j 0; j n; j) { grid[i][j] sc.nextInt(); } } int clicks 0; for (int i 0; i m; i) { for (int j 0; j n; j) { if (grid[i][j] 1) { clicks; dfs(grid, i, j, m, n); } } } System.out.println(clicks); } static void dfs(int[][] grid, int x, int y, int m, int n) { if (x 0 || x m || y 0 || y n || grid[x][y] 0) { return; } grid[x][y] 0; for (int[] d : dirs) { dfs(grid, x d[0], y d[1], m, n); } } }这段代码的优点是容易写、不容易漏方向。缺点是当矩阵很大比如 500 × 500 或者 1000 × 1000 且所有格子都是 1 时递归深度可能达到上万层Java 默认的线程栈可能会溢出。机考题如果明确说明 m、n 不超过 100 或者 200用递归没问题如果没给上限我个人更建议用 BFS 或者迭代 DFS。3.3 BFS 队列写法BFS 用 ArrayDeque 保存待扩展的格子每碰到一个新的 1就启动一轮队列扩散。代码量和 DFS 差不多但没有递归深度风险是机考环境下更稳妥的选择import java.util.ArrayDeque; import java.util.Scanner; public class Main { static int[][] dirs {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; public static void main(String[] args) { Scanner sc new Scanner(System.in); int m sc.nextInt(); int n sc.nextInt(); int[][] grid new int[m][n]; for (int i 0; i m; i) { for (int j 0; j n; j) { grid[i][j] sc.nextInt(); } } int clicks 0; ArrayDequeint[] queue new ArrayDeque(); for (int i 0; i m; i) { for (int j 0; j n; j) { if (grid[i][j] 1) { clicks; grid[i][j] 0; queue.offer(new int[]{i, j}); while (!queue.isEmpty()) { int[] cur queue.poll(); for (int[] d : dirs) { int nx cur[0] d[0]; int ny cur[1] d[1]; if (nx 0 nx m ny 0 ny n grid[nx][ny] 1) { grid[nx][ny] 0; queue.offer(new int[]{nx, ny}); } } } } } } System.out.println(clicks); } }BFS 的关键点在于入队之前就把格子置为 0。如果在出队的时候才置 0同一个格子可能被多个邻居重复放入队列虽然最终结果不受影响但会白白增加很多无效遍历极端情况下可能拖慢运行速度。3.4 复杂度与机考可用性这个算法的时间复杂度是 O(m × n)因为每个格子最多被访问一次空间复杂度在 BFS 里最坏是 O(m × n)因为队列可能装下一整片连通块。对机考里绝大多数题目来说这个复杂度都是足够的。就算棋盘到 1000 × 1000也就是一百万级别Java 跑完基本在一秒以内。机考时我最后提交的是 BFS 版本。原因很简单在不知道边界范围、网页编辑器又没有压力测试工具的情况下选一个理论上限更稳的写法比写一个更短的写法更让人安心。4. 读入、越界、自测三道最容易在考场上翻车的坎4.1 输入解析换行符、空格分隔、连续字符串这题最阴险的坑其实不在算法在输入。机考样题大多数给的是空格分隔的数字像这样子3 3 1 0 1 1 1 0 0 0 1但有些批次的输入格式是每一行直接给一个连续字符串3 3 101 110 001还有更隐蔽的坑用 Scanner 读完 m 和 n 之后如果不处理那一行结尾的换行符后面再用 nextLine 就会读到一个空字符串。很多 Java 考生在这里丢掉大量时间。我建议直接写一个兼容两种格式的读入函数一次搞定import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.StringTokenizer; public class Main { public static void main(String[] args) throws Exception { BufferedReader br new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st new StringTokenizer(br.readLine()); int m Integer.parseInt(st.nextToken()); int n Integer.parseInt(st.nextToken()); int[][] grid new int[m][n]; for (int i 0; i m; i) { String line br.readLine().trim(); if (line.contains( )) { StringTokenizer row new StringTokenizer(line); for (int j 0; j n; j) { grid[i][j] Integer.parseInt(row.nextToken()); } } else { for (int j 0; j n; j) { grid[i][j] line.charAt(j) - 0; } } } // 后面继续统计连通块逻辑 } }用 BufferedReader 还有一个额外好处当矩阵很大时它的读入速度明显比 Scanner 快不会在 IO 上成为超时点。这个技巧对机考的字符串处理题同样适用。4.2 边界与越界方向数组的四个判断别漏写 DFS 或 BFS 的扩展逻辑时最容易犯的错误是只检查了新坐标是否越界却忘了检查目标格子的值。反过来也会出错只检查值等于 1却忘了数组下标可能已经越界导致 ArrayIndexOutOfBoundsException。我的固定写法是先判越界、再判值两个条件用 连起来新坐标 x 必须满足 0 x m。新坐标 y 必须满足 0 y n。目标格子的值必须是 1。顺序看起来无关紧要但我习惯把越界判断写在前面。因为数组索引一旦越界后面的 grid[nx][ny] 根本访问不了先判值反而可能触发异常。这种细节在紧张状态下特别容易写反。4.3 “最小”两个字到底在问什么有些同学会纠结为什么不是模拟点击过程而直接数连通块其实这正是“最小”二字的含义。如果题目问“点击一次最多能消除多少个图标”那才是另外一道题问“最少点击几次清空”那就等价于数块数量。要验证自己的理解对不对我提供一个自测模板。准备下面几组输入分别跑一遍看输出是否符合预期输入矩阵期望输出原因3 3全 00没有图标不需要点击1 5全 11一整行都是同一块5 1交替 1 和 0点击次数等于 1 的个数上下不连通时每个 1 单独成块5 5棋盘全 11整个棋盘是一个连通块5 5只有中心一个 11单独一块机考平台的示例测试用例往往比较简单自己多构造几个边界用例能提前暴露不少问题。我当时就是先用“全 1”的大矩阵跑了一遍才确认 BFS 的入队逻辑没有重复计数。4.4 是否需要用 visited 数组这题其实不需要额外开 boolean[][] visited。因为点击消除后格子会从 1 变成 0而 0 本身就是“已经被处理过”的标记。外层循环碰到 0 会直接跳过。这样既省内存又少写一套维护逻辑。但要注意这种做法会直接修改原始输入矩阵。在机考题里无所谓反正输出结果跟原矩阵没有关系。如果你是在本地 IDE 里做调试想保留原始数据那可以加一个 visited 数组思路完全一样。5. 双机位下的 Java 编程纪律与 C 卷时间分配5.1 双机位监考下的合规动作别让监控误判双机位监考最折磨人的不是题难而是“被盯着”的压力。我总结了几条实际有用的动作规范不要频繁切换浏览器页面。就算有正当理由切屏次数一旦触发系统警告处理起来非常麻烦。不要低头看手机即使只是看时间。第二机位拍到低头动作监考端很可能放大画面检查。避免长时间手托下巴或遮挡面部。有人习惯做题时托腮思考这在人工复核时容易被误判。如果中途系统提示画面断开先报备再处理不要自己擅自关闭任何监考软件。另外机考网页编辑器一般不支持自动保存写几分钟就手动保存一下。Java 代码通过编译后也要尽快运行自测用例别攒到最后一起测那样出错后很难定位。5.2 Java 机考环境里值得默写下来的模板网页编辑器没有 IDE 的自动补全一些平时靠 IDE 帮忙的记忆突然就靠不住了。我最常默写的是三样东西主类结构、方向数组、读入模板。方向数组这个写法在网格题里几乎通用static int[][] dirs {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};另一个容易被忽略的是机考 Java 版本可能不支持一些很新的语法。我建议不要写 Java 8 之后的流式 API像 stream、var、List.of 这类特性版本不匹配会导致编译错误。老老实实用数组、ArrayList、Scanner、BufferedReader这些在任何版本都稳。输出方面System.out.println 足够。如果某些题目要求输出浮点数并保留两位小数直接 System.out.printf(%.2f, result) 也行。不要搞花活不要在输出里加多余空格和空行很多机考判题是严格比对输出内容的。5.3 C 卷时间预算以三道题 150 分钟为例我给自己定的时间预算是这样的第一题15 分钟。目标是全过测试用例。像“开心消消乐”这种连通块题读完题顺手把 BFS 模板默写出来提交自测后就不要过多纠结。第二题45 分钟。中等难度题通常是字符串处理、栈模拟、二分答案这类。先把暴力思路写出来如果时间紧张至少保证部分用例通过。第三题75 分钟。偏难题常见的是动态规划、图论、线段树。这一题允许拿不到满分但最好用暴力法写出能过的部分分支。剩余 15 分钟统一检查跑一遍第一题和第二题自造用例确认没有低级提交错误。实际执行下来我的“开心消消乐”大概用时 16 分钟比预算稍微超了一点主要浪费在摆弄手机支架和确认输入格式上。如果笔试前把读入模板背熟这个时间能压到 10 分钟以内。5.4 机考之后还有哪些环节机考只是华为 OD 流程的一部分通过之后通常还有性格测试、综合面试、技术面试等环节。硬要说“华为OD好进吗”机考这一关完全可以通过系统刷题来掌握节奏“开心消消乐”这类题本身就是典型的基础连通块题刷过一遍就能有肌肉记忆。但我也要提醒一句机考通过不等于万事大吉后续技术面试里 Java 基础、并发、Spring 相关的问题都会问到。热搜里那些“Java 八股文”“Java 判断字符串中是否不是字母和数字”“java排序”“java容器”的内容建议机考之后的复习阶段也要覆盖。最后复盘这道题给我留下的几个记忆点从“开心消消乐”这道题里我最深的体会是机考环境下第一题往往不是考你多聪明而是考你多稳。读入模板稳、主类结构稳、方向数组稳这三点做到位真正的算法思维只要十分钟就能完成。反而是那些双机位监考、输入格式不确定、网页编辑器没补全的附加压力才是实际考试里最消耗精力的地方。我后来再复盘这 16 分钟真正敲代码只用了五六分钟剩下的时间全在确认输入到底是空格分隔还是连续字符串。如果考前我把 BufferedReader 的通用读入函数写在本地备忘录里并在脑海里过一遍这个时间完全可以省掉。这就是我给各位备考 Java 方向朋友的最实际建议不要只背算法题解还要把工程模板背进手里。最后再分享一个小技巧不管题面包装成“开心消消乐”还是“打地鼠”还是“岛屿数量”只要看到二维矩阵 上下左右 连通关系第一反应就应该是连通块计数。代码写成 BFS输入用兼容解析自测用例多准备几个边界场景这道送分题就稳稳拿下了。双机位确实有点让人紧张但题目本身的“开心”程度取决于你提前准备了多少。