☰
华为OD机考100题深度解析:从题型拆解到刷题策略
2026/10/15 9:03:00 网站建设 项目流程

简介:这是一份面向具备一定编程基础、准备华为OD机考的考生整理的真题练习文档,从机考中精选100道经典题目并附参考答案,覆盖5键键盘的输出、N进制减法、报数游戏等高频题型,帮助考生熟悉考察重点与解题思路。资源包内含1个docx文件,约688KB,按题号顺序编排,每题包含题目描述、输入输出说明、示例与参考代码,便于逐题练习与对照复盘。文档中如5键键盘题涉及屏幕、选中区与剪贴板的状态维护,N进制减法题涉及进制转换、借位处理与前导零校验,均属机考常见考点。目前已有22682人学习下载,适合用于考前自测、查漏补缺与提升解题速度,建议先独立作答再翻看答案,以准确评估自身掌握程度。

1. 从一份 docx 说起:OD 机考 100 题到底值不值得刷

前阵子有个做 Java 的读者丢给我一份华为OD机考100题(含答案).docx,说投了几家 OD 岗,笔试连着挂了两轮,想让我看看这份题单靠不靠谱。我翻了一遍,第一反应是:这不是那种"背完就能过"的题库,而是一份把机考常见题型按难度铺开的练习集。它收录了 100 道题,从 5 键键盘输出、N 进制减法、TLV 解码,到 VLAN 资源池、按身高体重排队、报数游戏、比赛评分,覆盖了字符串处理、进制转换、模拟状态机、排序比较器、集合操作这几类高频考点,每题都配了可运行的 Java 参考实现。

它适合谁?适合有编程基础、准备走 OD 机考这条线的人。不适合谁?如果你连Scanner读输入、Comparable接口都没写过,直接刷这份题会很痛苦,因为答案里的代码默认你能看懂 Java 集合和基础语法。这份资源真正的价值不在"答案",而在题目本身把机考的输入输出格式、边界条件、异常处理套路摆得很清楚——机考挂人,十有八九不是算法不会,是输入解析和边界没处理干净。

2. 拆开看题型:这 100 题到底在考什么能力

2.1 三类高频题型的分布规律

把前几题过一遍就能摸到出题人的偏好。第一类是状态模拟,典型就是 5 键键盘输出。题目给你一个初始状态(空屏幕、空剪贴板、无选中),然后按顺序执行一串操作,问你最终状态。这类题不考算法,考的是你能不能把"选中""剪贴板""屏幕"三个变量的联动关系理清楚。第二类是进制与字符串转换,N 进制减法就是代表,输入是字符串形式的 N 进制数,要求你处理前导零校验、进制解析、负数符号输出。第三类是排序与比较器,按身高体重排队、比赛评分都属于这类,核心是写对compareTo的多级比较逻辑。

这三类题在真实机考里占比很高,因为它们在有限代码量内能同时考察语法熟练度、边界意识和逻辑严谨性。你刷这份题单,本质上是在练这三种肌肉记忆,而不是在背某道题的答案。

2.2 为什么答案里的代码值得逐行读

很多人刷题只看题目,答案扫一眼就过。但这份题单的参考实现有几个细节值得停下来看。比如 5 键键盘那题,作者用screen、choose、jianQie三个String变量分别表示屏幕、选中区、剪贴板,每次操作先判断choose是否为空,再决定是追加还是替换。这个结构比用数组或StringBuilder更直观,也更不容易在"选中状态下按 a 要先清空"这种规则上翻车。

再比如 N 进制减法,作者没有手写大数减法,而是直接用Integer.parseInt(str, jin)转十进制再相减,最后Integer.toString(res, jin)转回去。这个做法在题目限制的 100 字符以内是安全的,但如果字符数再大就会溢出。读答案的时候要能看出这种"在题目约束下够用,但换场景就崩"的边界,这才是刷题该有的收获。

2.3 输入解析:机考第一道隐形门槛

机考的输入格式和 LeetCode 完全不同。LeetCode 给你函数签名,机考给你一行字符串,你得自己split、自己parseInt、自己判断异常。这份题单里几乎每道题的第一段代码都是Scanner sc = new Scanner(System.in)加split,看起来简单,但实际考试里输入格式的花样很多:有的用空格分隔,有的用逗号,有的第一行是数量第二行才是数据,有的多行输入需要循环读取。

我一般会建议先写一个统一的输入读取模板,把常见的几种格式都覆盖掉,考试时直接套。下面这个模板是我自己常用的,处理了单行多参数和多行数据两种情况:

import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); // 场景一:单行多参数,空格或逗号分隔 String[] first = sc.nextLine().trim().split("[,\\s]+"); int n = Integer.parseInt(first[0]); int m = Integer.parseInt(first[1]); // 场景二:后续多行数据,每行一条记录 List<int[]> records = new ArrayList<>(); for (int i = 0; i < n; i++) { String[] parts = sc.nextLine().trim().split("[,\\s]+"); int[] row = new int[parts.length]; for (int j = 0; j < parts.length; j++) { row[j] = Integer.parseInt(parts[j]); } records.add(row); } // 后续业务逻辑 } }

split("[,\\s]+")这个正则同时兼容逗号和空格分隔,省得每道题都改。trim()是为了处理输入行首尾可能存在的多余空格,机考里这种脏数据不少见。records用List<int[]>存多行数据,比开二维数组灵活,因为有时候行数不确定。

提示:机考输入偶尔会在行尾带\r(Windows 换行),trim()能顺手清掉,不加的话parseInt会直接抛异常。

3. 拿 5 键键盘和 N 进制减法练手:从读题到跑通

3.1 5 键键盘:状态机怎么建才不乱

先把题目规则翻译成状态转移。屏幕内容用screen表示,当前选中内容用choose表示,剪贴板用clip表示。五个键的行为可以归纳成一张表:

按键编号前置条件行为
a1无若有选中,先清空选中并重置屏幕为 "a";否则屏幕追加 "a"
ctrl-c2有选中剪贴板 = 选中内容
ctrl-x3有选中剪贴板 = 选中内容,清空选中和屏幕
ctrl-v4剪贴板非空若有选中,先清空选中,屏幕 = 剪贴板;否则屏幕追加剪贴板
ctrl-a5屏幕非空选中 = 屏幕全部内容

这张表就是解题的核心。写代码之前先把表列出来,比直接上手写if-else靠谱得多。参考实现里用choose.equals("")判断是否有选中,用screen.length()输出最终结果,逻辑和表格完全对应。

public static void operate(int num) { if (num == 1) { // a 键 if (choose.isEmpty()) { screen += "a"; // 无选中,直接追加 } else { choose = ""; // 有选中,先清空选中 screen = "a"; // 屏幕重置为单个 a } return; } if (num == 2) { // ctrl-c clip = choose; // 复制选中到剪贴板 return; } if (num == 3) { // ctrl-x clip = choose; // 剪切:复制到剪贴板 choose = ""; // 清空选中 screen = ""; // 清空屏幕 return; } if (num == 4) { // ctrl-v if (choose.isEmpty()) { screen += clip; // 无选中,追加剪贴板内容 } else { screen = clip; // 有选中,替换为剪贴板内容 choose = ""; // 清空选中 } return; } if (num == 5) { // ctrl-a choose = screen; // 全选屏幕内容 } }

这段代码的关键在于每个分支都先处理choose的状态,再动screen。很多人写错是因为把顺序搞反了,比如 ctrl-v 时先追加再清空选中,结果屏幕内容就多了。参数方面,num直接来自输入的数字,不需要额外校验,因为题目保证输入合法。

3.2 N 进制减法:前导零和符号位是两个坑

这题看起来是数学题,实际是字符串校验题。题目明确说了:除了单独的 "0",被减数和减数不能以 0 开头。示例 2 里输入8 07 1,因为07有前导零,直接输出-1。这个校验必须在解析之前做,否则Integer.parseInt("07", 8)会正常返回 7,你就漏掉了错误。

// 前导零校验:长度大于 1 且以 0 开头即为非法 if (numStr.length() > 1 && numStr.startsWith("0")) { System.out.println(-1); return; } if (jianStr.length() > 1 && jianStr.startsWith("0")) { System.out.println(-1); return; } // 解析并计算 int a, b; try { a = Integer.parseInt(numStr, jin); b = Integer.parseInt(jianStr, jin); } catch (NumberFormatException e) { System.out.println(-1); // 字符不在进制范围内,解析失败 return; } int diff = a - b; // 符号位:0 表示正数或零,1 表示负数 System.out.print(diff >= 0 ? "0 " : "1 "); System.out.println(Integer.toString(Math.abs(diff), jin));

注意符号位的输出规则:题目示例里11 - 1 = 10输出0 10,符号位 0 表示结果为正。如果结果是负数,符号位输出 1,结果字符串输出绝对值。这里用Math.abs处理,避免Integer.toString对负数输出带-号的字符串。

注意:题目里提到的\0结尾是 C 语言风格的字符串终止符,Java 输入里不会真的出现,但如果你用其他语言实现,需要处理这个尾部字符。

3.3 本地验证:用示例输入跑一遍

写完代码别急着提交,先把题目给的示例全部跑一遍。5 键键盘的示例 2 输入1 1 5 1 5 2 4 4,期望输出2。手动推一遍:两个 a 后屏幕是 "aa",ctrl-a 选中 "aa",再按 a 清空选中屏幕变 "a",再 ctrl-a 选中 "a",ctrl-c 复制 "a",两次 ctrl-v 第一次替换屏幕为 "a",第二次追加变 "aa",最终长度 2。和代码逻辑对得上。

N 进制减法用示例 1 输入2 11 1,期望输出0 10。二进制 11 是十进制 3,减 1 得 2,转二进制是 10,符号位 0。跑通这两个示例,基本逻辑就没问题了。剩下的就是处理边界:输入为空、进制超范围、字符非法,这些在题目里都有说明,照着加校验就行。

4. 排序与集合类题目的避坑清单

4.1 多级比较器写不对,排序结果全乱

按身高体重排队这题,要求身高升序、身高相同按体重升序、都相同按原编号升序。参考实现里Player类实现了Comparable,但compareTo的写法有点绕:

@Override public int compareTo(Player ply) { if (ply.high > this.high) return -1; // 对方身高更高,自己排前面 if (ply.weight > this.weight) return -1; if (ply.idx > this.idx) return -1; return 0; }

这段逻辑是对的,但可读性差,容易看晕。更清晰的写法是用Integer.compare:

@Override public int compareTo(Player o) { int cmp = Integer.compare(this.high, o.high); // 身高升序 if (cmp != 0) return cmp; cmp = Integer.compare(this.weight, o.weight); // 体重升序 if (cmp != 0) return cmp; return Integer.compare(this.idx, o.idx); // 编号升序 }

Integer.compare(a, b)返回负数表示 a 小于 b,返回正数表示 a 大于 b,语义清晰,不容易写反。多级比较的套路就是逐级比较,某一级分出胜负就返回,全相等才返回 0。

4.2 VLAN 资源池:区间合并与拆分的边界

VLAN 资源池这题考的是区间操作。输入是1-5这样的区间和单个数字的混合,要求移除一个 VLAN 后重新输出。参考实现的思路是把所有 VLAN 展开成List<Integer>,排序后移除目标值,再重新扫描合并成区间。这个做法在 VLAN 数量不超过 4094 时没问题,但如果范围更大就会内存爆炸。

更通用的做法是不展开,直接在区间列表上操作:找到目标 VLAN 所在的区间,把它拆成左右两段,再合并相邻区间。不过对于机考来说,展开法够用且不容易写错,我一般会优先选简单的方案,除非题目明确限制内存。

// 展开区间到列表 List<Integer> list = new ArrayList<>(); for (String part : input.split(",")) { if (part.contains("-")) { String[] range = part.split("-"); int start = Integer.parseInt(range[0]); int end = Integer.parseInt(range[1]); for (int i = start; i <= end; i++) list.add(i); } else { list.add(Integer.parseInt(part)); } } Collections.sort(list); list.remove(Integer.valueOf(target)); // 注意用 Integer.valueOf 避免按索引删除

list.remove(Integer.valueOf(target))这里有个经典坑:如果直接写list.remove(target),当target是int时会调用remove(int index)按索引删除,而不是按值删除。必须装箱成Integer才能调对重载方法。

4.3 报数游戏:迭代器删除的正确姿势

报数游戏要求每报到 M 的人退出,直到剩余人数小于 M。参考实现用HashMap存编号和当前报数值,用Iterator遍历并在满足条件时remove。这里的关键是Iterator.remove()是唯一安全的遍历中删除方式,直接调map.remove(key)会抛ConcurrentModificationException。

Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry<Integer, Integer> entry = it.next(); entry.setValue(count++); // 更新报数值 if (entry.getValue() == M) { it.remove(); // 安全删除 count = 1; // 重新从 1 开始报数 } }

entry.setValue修改的是Map里的值,不是Iterator的副本,所以后续entry.getValue()能读到新值。这个细节如果不注意,可能会写出"报数值永远不更新"的 bug。

5. 比赛评分与 TLV 解码:两个容易被低估的细节

5.1 比赛评分:同分时的多级排序

比赛评分这题表面是算总分排前三,实际难点在同分处理:总分相同,比较 10 分个数;10 分个数相同,比较 9 分个数,以此类推。参考实现里Player存了List<Integer> scores,但compareTo只比较了总分,没有实现题目要求的同分规则。这是个隐藏 bug,如果测试用例里有同分选手,结果就会错。

正确的做法是在compareTo里加一层循环,从 10 分往下逐级比较:

@Override public int compareTo(Player o) { if (this.total != o.total) { return Integer.compare(o.total, this.total); // 总分降序 } // 同分:从 10 分到 1 分逐级比较个数 for (int score = 10; score >= 1; score--) { int cntThis = Collections.frequency(this.scores, score); int cntOther = Collections.frequency(o.scores, score); if (cntThis != cntOther) { return Integer.compare(cntOther, cntThis); // 个数多的排前面 } } return 0; }

Collections.frequency统计某个分数出现的次数,从高到低逐级比较,第一个不同的级别就决定顺序。这个写法比手动维护计数数组更简洁,代价是每次比较都要遍历scores,但在 M 不超过 10 的情况下完全可接受。

5.2 TLV 解码:小端序和字节对齐

TLV 解码这题的信息密度很高。Tag 占 1 字节,Length 占 2 字节且是小端序,Value 占 Length 个字节。输入是空格分隔的十六进制字符串,要求找到指定 Tag 并输出其 Value。

小端序的意思是低位字节在前。比如01 00表示长度 1,因为低字节01在前,高字节00在后。参考实现里用input[idx+2] + input[idx+1]拼接长度字符串,就是把两个字节按小端序还原:

int idx = 0; while (idx < input.length) { String tag = input[idx]; // 小端序:低字节在前,高字节在后 String lenStr = input[idx + 2] + input[idx + 1]; int length = Integer.parseInt(lenStr, 16); idx += 3; // 跳过 Tag(1) + Length(2) // 读取 Value StringBuilder value = new StringBuilder(); for (int i = 0; i < length; i++) { if (i > 0) value.append(" "); value.append(input[idx + i]); } if (tag.equals(targetTag)) { System.out.println(value); return; } idx += length; // 跳到下一个 TLV }

这里有个容易忽略的点:idx += 3之后,idx指向 Value 的第一个字节,读取length个字节后要再idx += length跳到下一个 TLV 的 Tag。如果漏掉这一步,循环就会卡在原地或者读错位置。

提示:题目要求输出的十六进制字符串不含小写字母,输入也不含小写字母,所以不需要额外做大小写转换。但如果你的解析逻辑里用了toLowerCase,记得转回来。

5.3 从这两题看机考的"隐藏分"

比赛评分和 TLV 解码这两题,代码量都不大,但都有"看起来对、跑起来错"的陷阱。比赛评分的同分规则如果没实现,简单用例能过,复杂用例就挂;TLV 解码的字节偏移如果算错,第一个 TLV 能解对,后面的全乱。机考判分是按用例通过的,一个边界用例没过就扣分,所以这类"隐藏分"题目反而比难题更值得花时间。

我自己的习惯是,每道题写完先构造三个用例:题目给的示例、一个边界用例(空输入或极值)、一个随机用例(手动推一遍预期结果)。三个都过了再提交,比直接提交然后反复调试省时间。

6. 刷这份题单的正确姿势:我的三遍法和一个后悔药

刷题最怕的是"刷完就忘"。我自己的做法是分三遍走,每遍目标不同。

第一遍叫裸做。不看答案,限时 30 分钟一道,写不出来就空着,但要把卡住的点记下来——是输入解析不会,还是算法思路没有,还是边界没考虑。这一遍的目的是暴露短板,不是追求正确率。

第二遍叫对答案。把第一遍空着的和写错的题拿出来,逐行读参考实现,重点看三个地方:输入是怎么解析的、状态是怎么维护的、边界是怎么处理的。读完不抄代码,合上答案自己重写一遍。如果重写还是卡,说明这个知识点没真正理解,标记出来第三遍重点看。

第三遍叫盲写。挑 10 道不同类型的题,不看题目不看答案,凭记忆把解题框架写出来。比如 5 键键盘,你要能默写出"三个变量 + 五个分支 + 选中优先"的结构;N 进制减法,你要能默写出"前导零校验 + 进制解析 + 符号位输出"的流程。这一遍过完,基本就能上考场了。

下面这张表是我按题型整理的刷题优先级,时间紧的话从高优先级开始:

题型代表题优先级理由
状态模拟5 键键盘高考逻辑严谨性,容易在边界翻车
字符串解析TLV 解码高考输入处理和字节操作,机考高频
排序比较器按身高体重排队高考 Java 基础,写错直接零分
进制转换N 进制减法中套路固定,练几道就熟
集合操作VLAN 资源池中思路简单但边界多,需要细心
数学模拟报数游戏中考迭代器使用,场景较窄

最后说一个我踩过的坑,算是后悔药。有次考试遇到一道字符串处理的题,输入里混了全角空格和半角空格,我split(" ")直接裂开,调试了十几分钟才发现。从那以后我每次读输入都强制走一遍trim()加正则split("[\\s]+"),不管题目看起来多简单都不省这一步。机考的输入数据不像 LeetCode 那么干净,多防一手不亏。

这份题单的价值在于它把机考常见题型的"标准解法"摆出来了,你不需要每道题都从零想,但一定要每道题都自己跑一遍。答案里的代码是参考,不是标准答案,有些地方(比如比赛评分的同分处理)还有改进空间。把它当成一面镜子,照出自己的薄弱点,比当成题库背下来有用得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询