news 2026/10/3 4:17:12

华为OD机试黑白棋考题全解析:常见变体与六语言实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
华为OD机试黑白棋考题全解析:常见变体与六语言实现

华为OD机试C卷里那道黑白棋,我见过好几个版本。有的考翻转判定,有的考包围计数,有的考最大连通块。名字都叫黑白棋,输入输出的格式差别却很大,解法思路也完全不一样。这篇文章就把这道题的常见出题方式、核心算法、六种语言的实现要点,以及双机位机试环境下的实操经验一次讲透。不管你是刚准备华为OD机试的求职者,还是想补一补模拟类算法题的思路,都值得看完。

先说一个最关键的认知:机试不是竞赛,抢时间比炫技重要。黑白棋这种题在OD机试里属于“中档偏易”的模拟题,考察重点不是高级数据结构,而是你能不能把规则描述转化成干净的代码,并且把边界情况处理对。C卷题目整体会比A卷、B卷更灵活一些,黑白棋经常以“翻转棋子”或“棋局计数”的形式出现,后面我会把每一种变体都说到。

1. 先认清这道“黑白棋”到底考什么

1.1 华为OD机试的基本盘

华为OD机试主要是线上算法笔试,采用的是牛客网或类似平台的自研环境,模式几乎都是ACM模式。也就是说,你不用像力扣那样补全一个函数,而是要自己处理完整的数据读取、计算、输出。这个区别特别重要,很多人刷惯了力扣的核心代码模式,到了机试现场连readline()和Scanner都忘了怎么用,这是最冤的丢分方式。

机试一般限时150分钟左右,做题数量通常是三到四道,难度递增。黑白棋这种题多数出现在第二题左右,属于“认真读题就能做出来”的那种。但正因为不难,它反而是拉分的点。你能不能满分通过,往往就取决于这类中等题写得稳不稳。

另外,机试成绩直接影响后续综测和面试节奏。虽然OD的流程不只看笔试,但笔试成绩太低,后面基本没机会解释。黑白棋这类模拟题,是性价比很高的提分目标。

1.2 黑白棋题目的三种常见出题方式

读题先不要急着写代码。我总结了一下,机试里的“黑白棋”大致分三类:

第一类是“落子翻转”型。给定一个棋盘,棋盘上有黑白棋子,某个位置是空位。你在这个空位落一颗子,按照黑白棋规则,把夹在对方棋子中间的所有对方棋子全部翻过来,最后输出翻转后的棋盘,或者统计翻转数量。这种最经典,也是本文重点讲的。

第二类是“最大翻转数”型。同样在棋盘上落子,让你遍历所有空位,计算如果落子能翻转的最大数量,并输出最佳落子位置。这类其实是在第一类基础上加一个枚举过程,逻辑没变,复杂度稍微高一点。

第三类是“连通块/包围”型。比如给定终局棋盘,让你数有多少个白子被黑子包围,或者统计最大连通块大小。这种其实是图论遍历题,跟普通翻转规则关系不大,需要用到DFS或BFS,还是要以题目描述为准。别看到“黑白棋”三个字就默认套Othello规则。

怎么快速区分?看样例。样例里如果出现每次操作后棋盘变化,那就是翻转类。如果只是问“包围了几个棋子”“最大连通块是几”,那就是搜索类。先花30秒读样例,比什么都强。

1.3 读懂输入输出的隐藏细节

这类题的输入格式相当固定但总有细节变化。常见格式是:

3 4 . W B . . W B . . . . .

3 4表示行数和列数,.表示空位,W和B分别表示白棋黑棋。但不同题目也可能用O和X、1和0、甚至*表示空位。你要以题目给的实际字符为准。一开始就把字符映射关系搞清楚,后面就不会白写。

输出方面,有的题目要求输出翻转后棋盘,有的要求输出数量,还有的要求输出最佳位置坐标。坐标起点是0还是1也要注意。这些细节看起来琐碎,但任何一个错了,样例都对不上,排查反倒浪费时间。

2. 核心算法思路:从暴力模拟到方向数组

2.1 先想清楚规则再动手

反过来先看翻转规则:你在空位落一颗黑子,然后沿上下左右、四个对角共八个方向扫描。拿一个方向举例:从落子点出发,如果紧挨着的是对方棋子(白子),继续沿这个方向走;一直走到遇到你的棋子(黑子)为止,那中间的这串白子全部翻成黑子。如果走到边界或者遇到空位都没见到自己的棋子,那这个方向一个也不能翻。

这里最容易踩坑的就是“遇到空位怎么办”和“遇到自己棋子之前先到边界怎么办”。处理办法是:用一个变量记录当前方向上是否见过对方棋子,如果最后遇到的是自己的棋子并且之前见过对方棋子,才执行翻转;否则跳过。这也符合黑白棋的基本规则——必须夹住才翻转。

为了把这种逻辑写得不重不漏,方向数组是标配。八方向的方向数组长这样:

int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; int dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1};

用方向数组的好处是,你不用复制八段几乎一样的代码。一个for循环跑8个方向,每个方向内部再用while循环延伸判断,结构清晰,书写量也少。生活化类比就是:把八个方向当成八条岔路,每条岔路走到底,能通就整段通车,不能通就原路撤回。

2.2 统计翻转数量的两个关键点

最大翻转型题目不是真让你修改棋盘,而是遍历每个空位,分别统计“如果在这里落子能翻转多少”,取最大值。这里的核心技巧是:统计时只计算数量,不实际修改棋盘;或者每次枚举时复制一份棋盘再模拟。前者更好,因为不修改棋盘的情况下,对每个空位的评估相互独立,不会互相影响。

统计每个方向的可翻转数量时,还有一个容易错的地方:当你沿着一个方向走到终止点后,需要判断“终止点是否在棋盘内且是己方棋子”。很多人的写法是循环里不断nx += dx[k],最后退出循环时nx已经越界了,然后直接用a[nx][ny]访问数组,直接越界报错。正确做法是:循环内部不断推进坐标的同时,每次先判断越界;最终判断终止点时,也要先判断坐标合法,再判断字符。这个顺序不能颠倒。

还有一点,如果题目棋盘规模很大,比如n、m能达到1000甚至2000,那么“每个空位枚举八个方向”的暴力解法复杂度大概是O(nm8*max(n,m)),最坏可能超时。但这种题目在OD机试里比较少出现,因为机试的模拟题一般数据范围给得很温和。不过看到大范围时,要会估算一下再决定是否优化,至少不能无脑暴力。

2.3 为什么这类题用模拟就够了

黑白棋题看着玩法复杂,但在算法层面它没有特殊结构,不需要图论、动态规划、高级数据结构。它就是一个二维数组上的状态转移模拟。之所以很多人做不出来,不是缺算法知识,而是对“如何把规则翻译成边界条件”不够熟练。方向数组+while循环+落点边界判断,这三个东西组合起来,你已经能覆盖市面上绝大多数的黑白棋变体。

我练习时通常会把“落子翻转”和“最大落子位置”放在一起做,因为后者完全复用前者的单点判断逻辑,只是外面套一层枚举。写熟一遍,两个题型就都吃透了。

3. 六种语言的实现要点:C/C++、Java、Python、Go、JS

华为OD机试支持的语言一般包括C、C++、Java、Python、Go、JavaScript等。下面我把每种语言在写这类题时最容易犯的幺蛾子列出来,这些都是真实考场上会被卡住的地方。

3.1 C/C++:用二维数组和最朴素的方式稳拿分

C++是最稳妥的选择之一,执行效率高,STL能省很多事。黑白棋题目建议直接用char二维数组,不需要用vector套vector,因为机试不追求优雅,追求不犯错。要注意的是读字符时,cin >> a[i][j]会自动跳过空白字符,这是好事,但如果你用了scanf("%c")就一定要处理换行。很多人读着读着就多读到一个'\n',最后数组错位,样例面目全非。

核心翻转函数可以直接参考这个结构:

#include <bits/stdc++.h> using namespace std; const int MAXN = 505; char grid[MAXN][MAXN]; int n, m; int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; int dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; int countFlip(int x, int y, char self, char enemy) { int total = 0; for (int k = 0; k < 8; k++) { int nx = x + dx[k]; int ny = y + dy[k]; bool seenEnemy = false; while (nx >= 0 && nx < n && ny >= 0 && ny < m && grid[nx][ny] == enemy) { seenEnemy = true; nx += dx[k]; ny += dy[k]; } if (seenEnemy && nx >= 0 && nx < n && ny >= 0 && ny < m && grid[nx][ny] == self) { int tx = x + dx[k]; int ty = y + dy[k]; while (tx != nx || ty != ny) { grid[tx][ty] = self; total++; tx += dx[k]; ty += dy[k]; } } } return total; }

如果只统计不修改,就把修改棋盘那一行删掉,累加计数即可。这样做的好处是,题目再怎么变,你只要改中间的循环退出条件就行。

如果用C语言写,没有STL,那就手动封装isInside之类的判断函数,其他思路完全一样。C语言的二维数组就是char grid[MAXN][MAXN],注意宏定义大小要比题目上限大一点点,防止越界。

3.2 Java:注意Scanner和nextLine的历史遗留问题

Java写这类题,技术栈上没有任何问题,二维数组操作顺手得很。最容易翻车的是输入读取。如果先用nextInt()读了n和m,后面再用nextLine()读棋盘行,你会惊讶地发现第一行读出来是空的。原因是nextInt()不会吃掉行尾的换行符,nextLine()会把那个残留换行读掉。

正确的姿势有两种。第一种,每次读完数字后立刻执行一次scanner.nextLine()吃掉残留换行;第二种,统一用next()读取每个字符,因为棋盘字符之间本来就有空格,next()自动按空白字符分隔,正好能逐个读入。我推荐第二种,干净利落。

另外,OD机试Java主类要求必须是Main,写public class Main,否则平台可能找不到入口。输出用System.out.println即可,注意不要输出多余的空格和调试信息。

3.3 Python:写起来最爽,但小心超时

Python在机试里非常受欢迎,因为代码量最少。黑白棋这种模拟题,逻辑转换最直观。比如判断八个方向时,可以把方向也写成列表推导式,代码能缩得很短。

不过Python有个隐患:如果棋盘规模偏大,纯Python的循环跑得慢,可能在极少数大样例上超时。机试平台一般Python的时限会比C++宽松一些,但依然不能掉以轻心。我的建议是:用sys.stdin.read()一次性读入所有内容,然后split()成字符串列表,再按索引读取。这比反复调用input()快很多,是一个成本极低又很实用的性能优化。

另外,Python的二维列表创建有经典坑:你要是写[['.'] * m] * n,那么每一行实际上引用的是同一个对象,改一个位置会联动所有行。必须使用列表推导式[['.' for _ in range(m)] for _ in range(n)]。这种问题在机试里一旦发生,排查起来很痛苦,因为样例规模小的时候可能看不出问题,数据一复杂就全乱套。

3.4 Go:切片和字节处理的取舍

Go在OD机试里也常见。Go的二维切片写起来比C++稍啰嗦一点点,但胜在内存安全,越界会直接panic,不会产生C++那种未定义行为。读取方面,如果棋盘字符用空格分隔,fmt.Scan按空格分割读取非常方便:

fmt.Scan(&n, &m) grid := make([][]string, n) for i := 0; i < n; i++ { grid[i] = make([]string, m) for j := 0; j < m; j++ { fmt.Scan(&grid[i][j]) } }

用string类型比byte类型更直观,比较时直接用== "W"就行。方向数组定义和C++类似,注意Go的数组声明方式即可。

有一个Go的坑是fmt.Scan在处理大量输入时性能一般,但黑白棋这类题的数据量通常不大,所以够用。如果实在担心,可以用bufio.NewScanner配合Split(bufio.ScanWords)来读,这样也比较舒服。

3.5 JavaScript:牛客网环境一定要会用readline

JavaScript在牛客网环境里走的是Node.js,输入输出跟浏览器里完全不一样。你没法用prompt,得用readline模块。很多不熟悉Node输入的人到这里就卡住了。

基本套路是:

const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); let lines = []; rl.on('line', (line) => { lines.push(line); }).on('close', () => { // 在这里写处理逻辑 let n = parseInt(lines[0].split(' ')[0]); let m = parseInt(lines[0].split(' ')[1]); let grid = []; for (let i = 1; i <= n; i++) { grid.push(lines[i].split(' ')); } // 后续逻辑 });

这里要注意split(' ')是按单个空格拆。如果输入行里有多个连续空格,更稳妥的是split(/\s+/)。JavaScript的数组操作很自由,方向数组写成二维数组就行。处理完结果后别忘用console.log输出。

另一个容易忽略的点是:牛客网有些题目输入可能有多行末尾空格,split(/\s+/)在空字符串上会返回空数组,要用filter(Boolean)清理一下,避免误判。这部分我吃过亏,写在这里提醒你。

4. 双机位机试的实操避坑指南

4.1 双机位到底怎么搭才合规

华为OD机试近年来普遍要求双机位监控。主机位就是你的答题设备,摄像头要拍到人脸和上半身,工作台要干净,桌面上不能放手机、平板、纸质资料。第二机位通常要求放在侧面或侧后方,能拍到你的双手、屏幕和桌面环境。调试的时候不要太随意,我见过有人用手机支架夹在床沿,角度没调好,拍到一半画面被手臂挡住,考试中途被监考提醒,非常影响心态。

光线也很关键。逆光会导致人脸发黑,监考看不清容易误判。尽量让光源从正面或侧前方来,摄像头能清楚看到你的表情和手部动作。背景不用刻意收拾成书房,但最好不要有大量露出个人信息的东西。

考前一定跑一遍完整流程:打开答题平台、测试摄像头、授权麦克风(有些监考系统会要求语音)、确认第二机位的角度和续航。手机的话,准备好充电线,别考到一半没电黑屏。

4.2 ACM模式输入输出:练熟三种固定套路

机试和力扣最大的不同就是输入输出得自己写。黑白棋这种二维矩阵题,我建议你提前把三种常见读法练到条件反射:

第一种是“先读两个整数,再读n行由空格分隔的字符”。C++用cin,Java用next(),Python用sys.stdin.read().split(),Go用fmt.Scan。这四种都要能闭眼写出来。

第二种是“先读一个整数表示用例数,然后循环读若干组棋盘”。这种套路需要你额外维护一个外层循环,并在每轮结束后正确重置棋盘。很多人第一组数据能过,第二组就开始串数据,基本就是外层循环变量没处理好。

第三种是“输入是不定长的,读到EOF结束”。这种在JS里尤其常见,要利用readline的close事件统一输出结果。

这三种读法练熟后,你遇到任何模拟题,输入部分都不会成为障碍。黑白棋只是一道二维矩阵题,它不会考你字符串解析的边角料,但你如果连读入都生疏,写起来就特别掉速。

4.3 机试中的调试技巧:别在系统里反复试错

机试环境里虽然能编译运行,但你不可能像本地IDE那样随意打断点。我常用的调试思路是“小样例人工模拟一下,再跑程序对比”。

以黑白棋为例,先构造一个2行2列或3行3列的超小棋盘,手动在草稿纸上把翻转过程画一遍,得出预期结果,然后跑代码看输出是否一致。不一致的话,用print或cout把每个方向的终止坐标打印出来,基本一眼就能看出边界判断错在哪里。

注意:调试信息在最终提交前必须清干净,平台是按标准输出判断结果的,你输出一行调试日志,整个答案就废了。这是机试里最傻的丢分方式,没有之一。

另外,机试过程中如果网络不稳定,可能导致提交失败。遇到这种情况别慌,先检查代码是否依旧保存在编辑器里,然后刷新页面重新提交。但更靠谱的做法是提前选一个网络稳定的环境,优先用有线网络,尽量别用公共Wi-Fi。我之前就碰到过考试途中Wi-Fi信号忽高忽低,心态直接受影响,从那以后每次远程考试都用网线。

5. 黑白棋题型的拓展与复盘

5.1 从“翻转”到“连通块”:同一个题型的延伸

如果你做完黑白棋翻转题还有时间,我建议顺手把“连通块”变体也练一下。它的常见问法是:终局棋盘上,某个颜色的棋子被另一种颜色完全包围的区域有多少个,或者最大连通块包含多少个棋子。这种题需要DFS或BFS,虽然和翻转规则无关,但它在“黑白棋”的名义下经常被混着考。

我的练习方式是这样的:把“翻转棋盘”和“计算最大连通块”放在同一个训练周期里,因为两者的二维遍历和边界判断套路高度重合。写熟了方向数组和visited数组,你就能在两类题目之间快速切换,而不会产生知识点割裂感。

5.2 复盘一次完整的模拟实战

假设题目要求:给定一个5行5列的棋盘,输入若干步落子操作,每次输出翻转后的棋盘,直到所有空位填满或落子处不是空位则跳过。这种多步操作题更接近现实博弈,写起来也更考验代码组织。

我的做法是先写一个主循环,处理每一步输入,再封装三个函数:isValid判断落子是否合法、countAndFlip执行翻转、printBoard输出结果。每个函数只做一件事,主循环保持清爽。这样即使某一步出bug,也能很快定位到具体函数。

实际测试时,我习惯用两个用例:第一个是最简单的角落落子,第二个是三个方向同时夹住棋子的情况。角落落子能验证越界判断,三方向夹击能验证循环终止逻辑。这两个用例过了,基本就能放心提交。

5.3 关于华为OD的其他“关卡”

机试只是OD流程的入口之一。黑白棋这道题做对了,你才有后面的综测、技术面试、HR面试和主管面试。网上有人问“华为OD好进吗”,我的体会是,它比社招进大厂正编门槛友好一些,但竞争在逐年上升,机试拿高分能明显增加后续谈判的底气。尤其是C卷的题库更新以后,类似黑白棋这种中等模拟题,已经成了很多人拉开分差的关键。

准备的时候也不要迷信刷题数量。理解一道题从“暴力翻译规则”到“合理封装函数”的整个过程,比背十道模板题更有用。黑白棋这道题就是很好的练手点,它规则直观、代码量适中、边界情况丰富,非常适合用来检测你的ACM模式基本功。

最后再分享一个小技巧:练题时不要只练你会写的语言,尽量把C/C++、Python、Go各写一遍,因为不同语言对二维数组、方向数组、输入输出的组织方式不一样。多写一遍,你对题目本身的理解就会深一层。等你真的坐在双机位面前,打开编辑器的那一刻,手已经比脑子更熟悉这道题的套路了,那才是最好的状态。

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

OpenShell:集成SSH会话管理与多主机分组的终端工作台

很多人看到“OpenShell”这个名字&#xff0c;第一反应以为是给传统 Shell&#xff08;比如 Bash、Zsh&#xff09;加了个开源外壳&#xff0c;或者是个什么命令行美化工具。其实它更像是一个“终端工作台”&#xff1a;把终端模拟器、SSH 会话管理、多主机分组、标签页组织这些…

作者头像 李华
网站建设 2026/10/3 4:16:29

基于SpringBoot+Vue的共享图书管理系统设计与实现全解析

又到一年毕设季&#xff0c;后台私信里"Java毕设做什么题目好"这类问题又多了起来。翻来覆去&#xff0c;我总会重点推荐一个方向——基于SpringBootVue的共享图书管理系统。原因很简单&#xff1a;这个题目难度适中&#xff0c;业务逻辑清晰&#xff0c;前后端技术栈…

作者头像 李华
网站建设 2026/10/3 4:15:40

Java服务在Docker中内存泄露排查实战:从jstat到MAT

那会儿我刚接手一个Java后端服务&#xff0c;它在Windows上的Docker Desktop里跑着。第一周一切正常&#xff0c;到三四天后&#xff0c;容器监控曲线开始一路向上&#xff1a;从刚启动时的800M&#xff0c;慢慢爬到了1.8G。第一反应是WSL2或者虚拟化层的缓存捣鬼&#xff0c;查…

作者头像 李华
网站建设 2026/10/3 4:15:36

优先队列详解:从堆原理到Top-K与工程实战

优先队列&#xff1a;不只是“排队”&#xff0c;更是算法的隐形加速器在写业务代码时&#xff0c;我们经常跟“队列”打交道&#xff1a;先来先服务&#xff0c;FIFO&#xff0c;公平得很。但现实世界里&#xff0c;很多场景根本不讲“先来后到”&#xff0c;而是讲“谁的优先…

作者头像 李华
网站建设 2026/10/3 4:15:17

裸机与Linux中断处理流程对比:从执行路径到驱动实现

第一次从裸机项目切到带 Linux 系统的嵌入式板子时&#xff0c;我反复问自己一个问题&#xff1a;同样是跑一个流水灯&#xff0c;为什么裸机上直接写寄存器就行&#xff0c;Linux 下却非要写内核驱动&#xff1f;后来排查一起中断丢失问题时&#xff0c;我才彻底想明白——有操…

作者头像 李华
网站建设 2026/10/3 4:14:19

质子交换膜燃料电池Comsol多物理场仿真完整建模指南

做氢电仿真这几年&#xff0c;我最深的体会就是&#xff1a;质子交换膜燃料电池的Comsol模型&#xff0c;上手容易做好难。不信你去看看&#xff0c;现在氢电相关的文章确实发得不少&#xff0c;但大多数模型停留在单电池、单物理场、稳态工况的层面&#xff0c;真正能把电化学…

作者头像 李华