先说个比较现实的判断:华为机试这块,已经不是“会不会写代码”的门槛,而是“能不能在三道题里稳定拿分”的筛选器。很多人刷题只看数量,不研究题型规律,上了考场才发现连输入格式都能耗掉二十分钟。所以这篇博文我尽量把模拟题背后真正的考点逻辑、踩坑点和实操策略讲透,尤其是华为OD机试的新系统双机位C卷,环境变化大,刷模拟题的方式也得跟着变。
我自己练这套模拟题时,最大的感受是:华为机试的算法题并不追求炫技,它考的是在最基本的数据结构和算法里,你能不能快速找到正确思路并写出健壮代码。这也决定了刷题方向——贪心、双指针、DFS/BFS、动态规划、排序、字符串处理,这些是绝对的主战场。
适合谁来参考?准备华为OD机试或正式岗位机试的开发者,尤其是中途转码、或者离开算法题很久想快速找回手感的人。如果你刚刷完LeetCode hot 100,想了解华为机试的出题口味,这篇也能帮你省不少弯路。
1. 华为机试到底考什么:从三题结构到得分逻辑
1.1 三道题的分值与目标策略
华为机试(含OD机试)通常是三道题,分值分布大致是100分、100分、200分。不同部门、不同批次会有微调,但整体结构非常稳定。核心逻辑是:前两道题拼速度和细心,第三道题拼算法深度和代码熟练度。
我见过很多人的策略是死磕第三题,结果前两题因为输入输出的小错误丢了分,总分反而不如老老实实把前两题拿满的人。这里直接给结论:前两题必须满分,第三题尽量拿部分分。原因很简单,机试的判分是按用例通过率来的,第三题即使做不出来,暴力解法能过30%到50%的用例,总分依然可观。
很多人在模拟题里不重视暴力解,觉得“AC不了就是不会”。但机试跟竞赛不一样,它是通过性考试,不是排名赛。你能在第三题写出一个正确的暴力解法,然后用剩余时间优化,这个节奏远比一头扎进最优解里出不来要合理。
1.2 新系统双机位C卷环境变化
这里多说一句华为OD机试的新系统。C卷时代,机考环境改成了双机位监控,手机角度、电脑屏幕、甚至周围环境都有要求。这个变化直接影响的是你的“考场手感”——没有刷题时的舒适状态,代码写得再顺也可能因为环境检查浪费时间。
我建议大家平时模拟时就养成两个习惯:第一,用牛客网的ACM模式刷题,不要用LeetCode那种直接填函数体的模式;第二,练习时开一个计时器,按真实考试的时间节奏来。华为机试的代码是需要在本地编辑器写好再粘贴,还是直接在网页里写,不同批次不太一样,但无论哪种,你都得习惯“没有IDE辅助提示”的裸写状态。
这里真正要练的,是你在没有自动补全、没有语法高亮的情况下,能不能一次写出没有低级语法错误的代码。这个能力,恰恰是刷模拟题时最容易忽略的。
2. 高频考点拆解:模拟题的出题逻辑是什么
2.1 字符串处理与正则匹配
华为机试的第一题几乎必考字符串处理,难度不高但细节极多。常见的出题方式有:字符串分割、字符统计、括号匹配、子串反转、RLE编码解码、敏感词替换等。
这类题的核心考点不是算法,而是边界处理能力。比如输入可能带前后空格、分隔符可能连续出现、字符串可能为空,这些情况若不在代码里显式处理,很容易在隐藏用例上翻车。
我建议大家在做字符串题时,统一用一套固定的处理模板:先用strip()去掉首尾空白,再按分隔符split(),然后判断分割后的列表长度和空字符串。这三个步骤看起来基础,但能解决80%的字符串边界问题。
另外一个容易忽略的点是Python的split()和split(' ')区别——前者会自动合并连续空格,后者不会。如果你拿到的题目输入是“多个空格分隔”,用split()反而更安全。这种细节,只有真正刷题踩过坑才会记住。
2.2 排序与自定义比较器
排序在华为机试中出现频率极高,但通常不是让你直接调库,而是要求按规则排序。比如按字符串长度、按指定键值排序、按组合数字的大小排序等。
Python里实现自定义排序的核心是functools.cmp_to_key。很多人不熟悉这个函数,导致在“按规则排序”类题目里卡壳。其实它就是把C++的compare函数迁移到Python里,你只需要写一个“返回负值表示a在b前面”的函数即可。
举个我在模拟题里碰到的例子:给定几组数字,要求将它们排列成一个最大的数。这本是一道经典贪心题,核心思路是比较两个数字的拼接结果,比如a=3, b=30,因为'330'大于'303',所以3应该排在30前面。这个思路如果不用cmp_to_key,实现起来会很别扭,但用了之后代码量骤减。
有同学会问,这种题考得有意思吗?我觉得它的价值在于:你不会只调list.sort(),而是能理解排序背后的“比较规则”可以被自定义。这个能力在真实工程项目里同样重要——排序不只是按数字大小或字典序,绝大多数业务场景都带着自己的规则。
2.3 DFS/BFS与动态规划
第三题的常客就是搜索和动态规划。DFS考的是递归与回溯,BFS考的是状态扩展与最短路径,DP考的是状态定义与转移方程。
我刷模拟题的体会是,华为机试的搜索题往往带着“地图”或“迷宫”背景,比如机器人走格子、岛屿数量、最短路径中是否有障碍物。这类题用BFS基本能在可接受时间内解出,关键是队列里存什么状态、什么时候标记访问、如何判重。
动态规划常考的则是背包问题变体、最长递增子序列、编辑距离,以及一些状态压缩的简化版。机试中的DP不会太难,但很考验你是否能快速看出这是DP题。我的判断标准很简单:如果题目问的是“最多/最少/多少种方案”,且当前状态可以由之前的状态推导出来,八成就是DP。
3. 实战演练:三道模拟题的完整解题过程
3.1 字符串压缩题:从暴力到优化
题目描述大概是:给定一个字符串,将连续重复的字符压缩成“字符+连续出现次数”的形式,如果压缩后的长度不小于原字符串,则返回原字符串。比如aabcccccaaa压缩后是a2b1c5a3。
这题的核心思路很简单,一次遍历,记录当前字符和计数。但有一个坑:压缩后的格式不一定比原字符串短。题目要求是压缩后不小于原串就返回原串。很多人做到一半忘了这个条件,导致结果不对。
def compress(s: str) -> str: if not s: return "" res = [] cnt = 1 for i in range(1, len(s)): if s[i] == s[i - 1]: cnt += 1 else: res.append(s[i - 1] + str(cnt)) cnt = 1 res.append(s[-1] + str(cnt)) compressed = "".join(res) return compressed if len(compressed) < len(s) else s代码很简单,但有三点值得展开说:第一,for循环的起点是1,而不是0,因为你要和上一个字符比较;第二,循环结束后还要补上最后一组字符的统计,这是最容易漏的;第三,最后要比较压缩前后的长度,不能无脑返回压缩结果。
这道题我在模拟题里给过自己一个要求:两分钟内写完并保证通过所有用例。因为它的逻辑太清晰了,如果花更长时间,说明代码书写的基本功还不够扎实。
3.2 任务调度题:贪心还是优先队列?
再来看一道常见题:给定一堆任务,每个任务有执行时间,同一时刻只能执行一个任务,但任务的顺序可以任意安排,问平均等待时间最短的调度策略是什么。
这就是典型的“最短作业优先”问题。贪心策略是先执行执行时间最短的任务。但要证明这个贪心策略的正确性,需要一点数学推导:如果两个任务执行时间分别为a和b,且a>b,那么先a后b的等待时间是2a+b,先b后a的等待时间是a+2b,差别在于谁被多等了一次。显然先短任务更优。
实际题目中往往会有变体,比如任务还有优先级、有截止时间、或者有依赖关系。遇到依赖关系时,贪心就不一定有效了,这时候往往要转成拓扑排序或DP。
我用优先队列实现时,有个心得:不要试图在插入时排序,直接用堆。Python的heapq非常方便,插入和弹出都是O(logn),整体复杂度是O(nlogn),足够应对机试的数据量。
import heapq def min_waiting_time(tasks): heapq.heapify(tasks) total = 0 cur = 0 while tasks: t = heapq.heappop(tasks) cur += t total += cur return total // len(tasks)这里total累计的是每个任务完成的时间点,而每个任务的等待时间就是从开始到它完成的时间。这个细节很多人会搞混,写成累加t本身,那样算出来的就是错的。
3.3 迷宫最短路径题:BFS的通用写法
迷宫题的描述通常是这样:给定一个二维矩阵,0表示可以走,1表示墙,起点在右上角,终点在左下角,求最短路径长度,如果不可达则返回-1。
BFS是解这个问题的标准方法,核心在于“队列+访问标记”。我总结了一个模板,适用于几乎所有BFS题:
from collections import deque def shortest_path(grid, start, end): rows, cols = len(grid), len(grid[0]) visited = [[False] * cols for _ in range(rows)] q = deque([(start[0], start[1], 0)]) visited[start[0]][start[1]] = True directions = [(1, 0), (-1, 0), (0, 1), (0, -1)] while q: x, y, step = q.popleft() if (x, y) == end: return step for dx, dy in directions: nx, ny = x + dx, y + dy if 0 <= nx < rows and 0 <= ny < cols and not visited[nx][ny] and grid[nx][ny] == 0: visited[nx][ny] = True q.append((nx, ny, step + 1)) return -1这个模板的关键点有几个:一是队列中存的三元组(x, y, step),step表示从起点到当前点的距离;二是入队时立即标记visited,而不是出队时标记,这样能避免同一个点被多次入队;三是四个方向的偏移量,适合上下左右移动的题目。
有同学问为什么BFS能保证第一次到达终点时路径最短。因为BFS按层扩展,每一层的步数都是相同的,第一次扩展到终点时,队列中前面的节点都已经用当前步数处理过了,所以这个路径一定是最短的。理解了这个原理,你就不用死记硬背了。
4. 机试中的输入输出与细节处理
4.1 ACM模式下的输入模板
华为机试用的是ACM模式,也就是自己写输入输出。很多人刷模题时用LeetCode模式惯了,一到ACM模式就不知道如何处理输入。这里给出两个常用的Python模板。
如果输入是一行一个整数:
import sys for line in sys.stdin: line = line.strip() if not line: continue n = int(line) # 处理逻辑如果输入是多行,第一行是数据个数,后面是具体数据:
import sys data = sys.stdin.read().strip().split() n = int(data[0]) arr = list(map(int, data[1:1 + n]))第二个模板用sys.stdin.read()一次性读取全部输入,再按空格分割,对于输入格式复杂的题特别方便。我推荐大家养成用read()读取的习惯,因为它能避免for line in sys.stdin在最后一行没有换行符时漏读的问题。
4.2 边界条件:机试最隐蔽的扣分点
机试判题按用例通过率给分,隐藏用例往往专门卡边界条件。我梳理了几个常见的边界陷阱,这些在模拟题里反复出现:
- 输入字符串可能包含首尾空格或空串
- 数组长度可能为0或1
- 图可能不连通
- 数字可能为负数或极大值
- 结果可能溢出Python不做限制,但其他语言要注意
处理边界的习惯是:在写代码之前,先想清楚“输入的合法范围是什么”,然后在代码开头统一做防御性判断。很多人觉得这样浪费时间,但一个边界条件没处理,就可能让20%的用例挂掉,比一道题做不出来更可惜。
4.3 时间复杂度估算与超时规避
华为机试通常有执行时间限制,C++一般在1秒到2秒,Python会放宽一些,但依然有压力。实际机试中,如果你用的是O(n^2)的算法,n在10^4到10^5之间,Python很可能超时。
我给大家一个参考标准:Python每秒大约能处理10^7次简单运算。所以:
- n <= 10^3,O(n^2)可以
- n <= 10^5,需要O(nlogn)或以下
- n >= 10^6,基本只能O(n)
做题时先用这个标准快速估算一下,如果发现自己的算法可能超时,就要考虑换思路或优化。比如暴力双重循环改成哈希表,或者排序后双指针。
5. 模拟题中反复出现的坑与排查技巧
5.1 递归深度导致的运行时错误
DFS用递归实现时,Python默认的递归深度上限是1000左右。如果递归深度超过这个值,程序会直接报RecursionError。这在图遍历、岛屿数量等问题中很容易触发。
解决办法有三个层次:第一,用sys.setrecursionlimit(10000)把限制调高;第二,改成迭代方式的DFS,自己维护栈;第三,改成BFS,天然没有递归深度问题。我建议在第三题遇到DFS时,直接用栈模拟递归,虽然代码量稍多,但稳定性最好。
5.2 测试用例自己怎么补
机试题目给出的示例通常很少,可能只有一两个。举例来说,如果题目给了一个数组求最大值,很多人只看正数情况,忽略了全部负数的情况。但隐藏用例一定会包含这种边界场景。
我的做法是,在写完代码后,立即构造几组特殊输入自己测:最大值、最小值、空输入、只有一个元素、所有元素相同。这个习惯能让你在提交前就发现潜在的问题。我统计过,这个步骤至少能帮我挽回10%到20%的用例通过率。
5.3 超时后如何快速优化
如果你发现自己的代码超时,先不要推翻重写。按照下面的顺序排查:
- 有没有重复计算?把固定的计算结果提前存好
- 有没有不必要的遍历?用哈希表或前缀和优化
- 数据结构选对了没?列表查找是O(n),集合是O(1)
- 能否用双指针/滑动窗口替代一部分循环?
大多数超时问题都能通过这四步解决。如果都不行,那就考虑换一种算法思路,比如从DP改为贪心,或从暴力改为二分。切忌冥思苦想不动手,机试时间不等人。
6. 从模拟题到考场:心态与节奏的复盘
我把这套模拟题完整做下来之后,最大的收获不是某道题的解法,而是整个做题节奏的把控。我自己总结了一套“前紧后松”的策略:前两道简单题快做,做完后立刻检查输入输出和边界,不急着提交;第三道题先读三遍题,把最暴力的解法写出来,保证有基础分,再考虑优化。
实际操作中,我会在考试还剩30分钟时强制停止所有优化工作,把已经写完的代码做最后的完整测试。因为最怕的不是写不出最优解,而是在最后关头改代码改出bug。稳,才是机试的制胜法宝。
最后分享一个不算技巧但很管用的经验:平时模拟刷题时,不论题目难易,都给自己限定时间。简单题15分钟,中等题25分钟,难题40分钟。时间到了还没AC,就直接看题解,搞清楚思路后自己再手写一遍。这样既能保持刷题效率,也能模拟考场的真实时间压力。