华为机试这东西,圈内人都懂,它不叫什么“算法竞赛”,也不讲什么“项目经验”,它就是一杆秤,直接称你写代码的基本功。最近不少人在刷“华为机试编程模拟题5”这类套题,还有人私信问我OD机试新系统、双机位C卷到底什么难度、该按什么顺序刷题。正好我手上刚带完一批备考的朋友,把第5套模拟题从头到尾撸了一遍,也总结了不少踩坑经验。这篇就把这套题的拆解思路、编码实现、答题策略和常见翻车点一次性说清,不管是刚入门准备机试的,还是刷题刷到瓶颈想提速的,应该都能在里面找到点有用的东西。
先说明一下,我拿到的“模拟题5”是一个典型的三题组合卷,覆盖了字符串处理、贪心/模拟、以及基础动态规划三种类型。题量不算大,但很能体现华为机试的命题风格——不考偏题怪题,考的是你在有限时间内,能不能用最稳妥的方式把一道业务味道很浓的题目写对。你要知道,华为机试改卷是不看代码风格的,只看测试用例过没过,所以你写的代码丑一点没关系,思路稳、边界全、能AC,就是王道。
1. 内容整体设计与思路拆解
1.1 华为机试到底在考什么
在聊模拟题5之前,有必要先把机试的底层逻辑捋一遍。华为机试一般时长2小时,3道题,分值分布大约是100分、200分、300分,总分600分。分数线和你的目标部门、岗位级别挂钩,但通常来说,100分那道题必须拿满,200分的尽量拿满,300分的至少过部分测试用例。这个策略非常重要,因为很多人在第三题死磕到底,结果前面简单的题反而因为粗心丢了分。
从题型分布来看,华为机试的题库虽然庞大,但考点高度集中:字符串处理、排序、滑动窗口、双指针、贪心、模拟、动态规划、DFS/BFS、并查集、前缀和。相比ACM比赛,华为机试更看重“能把问题拆解成代码逻辑”的能力,而不是各种高深的数据结构。你甚至可以不用红黑树、不用线段树、不用KMP,老老实实用数组、哈希表、双循环也能通过大部分题目。
1.2 为什么建议按模拟卷刷题
很多备考的同学喜欢按专题刷题,今天刷字符串,明天刷DP,后天刷图论。这种方式能夯实知识点,但如果临近考试,我强烈建议你改成刷整卷模拟题。原因很简单:机试的难度不在单个题目,而在状态切换。你前面刚写完一个字符串处理,脑子还停留在字符数组的思维里,下一题突然跳到动态规划,思路能不能快速切过来,这需要练习。
我在带人刷题的时候一直强调:模拟题的价值不是“对答案”,而是“模拟考场状态”。你按考试时间和规则把一套卷子完整做下来,然后复盘哪里卡住了、哪里超时了、哪里边界漏了,这套流程才是真正提升分数的关键。模拟题5就是一套非常适合用来练考场状态的卷子,它的难度梯度合理,题型覆盖典型,部分题目甚至可以说是真题的换皮版本。所以如果你已经刷过一遍基础算法,强烈建议现在开始按套卷来冲刺。
1.3 模拟题5的整体难度评估
先说结论:模拟题5的整体难度,在市面流传的各种模拟卷里属于中等偏上一点点。第一题是字符串处理,难度不大但极其容易踩坑;第二题是带一点贪心思想的模拟题,需要排序和前缀和的配合;第三题是一个背包问题的变种,考察状态转移的基本功。
这套卷子很典型地反映了一个趋势:现在的华为OD机试(新系统、双机位C卷),并不追求题目多么惊艳,反而更青睐“把经典题目包装成实际业务场景”的考法。比如字符串题会套一个日志解析的外壳,背包题会伪装成资源分配问题。所以你刷题的时候,不要只背代码模板,要锻炼自己“识别题目本质”的能力。看到一个场景,能快速翻译成算法模型,这才是高分的关键。
2. 核心细节解析与实操要点
2.1 第一题:字符串解压缩(难度:简单偏中)
这道题我记得很清楚,题目是给定一个压缩后的字符串,比如“3[ab]2[c]”,要求输出解压后的结果“abababcc”。看似简单的字符串处理,其实隐藏着两个大坑:第一个坑是数字可能不止一位数,比如“12[ab]”;第二个坑是括号可能是嵌套的,比如“2[a3[b]]”。
嵌套结构一出现,熟悉的朋友立刻会想到栈。但如果直接用栈写,代码量不小,而且处理数字拼接时容易出错。我建议用递归下降法,这样思路更清晰,而且嵌套深度在机试中通常不会太大,直接用递归也不会爆栈。
#include <stdio.h> #include <string.h> char s[100005]; int pos, len; // 解析从当前位置开始的一个单元 void parseUnit() { if (pos >= len) return; if (s[pos] >= '0' && s[pos] <= '9') { int num = 0; while (pos < len && s[pos] >= '0' && s[pos] <= '9') { num = num * 10 + (s[pos] - '0'); pos++; } // 跳过 '[' pos++; while (pos < len && s[pos] != ']') { parseUnit(); } // 跳过 ']' pos++; for (int i = 0; i < num; i++) { for (int j = 0; j < curLen; j++) { putchar(tmp[j]); } } } else { // 普通字符直接输出 tmp[curLen++] = s[pos]; pos++; } }这个版本我做了简化,实际写的时候需要用一个全局缓冲区暂存当前括号内解析出的内容,然后根据倍数重复输出。这个思路其实很清晰:看到数字就解析数字,然后递归处理括号里的字符串,遇到普通字符就直接输出或暂存。边界条件要特别注意,数字解析完之后,下标要移动到左括号之后,右括号处理完之后下标也要正确移动。
2.2 第二题:任务调度与最大收益(难度:中等)
第二题是一个典型的贪心+排序问题,题面包装成“有多个任务,每个任务有截止时间和收益,每个单位时间只能做一个任务,求最大收益”。这题的经典解法是:按截止时间从小到大排序,然后用一个小根堆维护已选任务的收益,一旦发现当前已选任务数超过截止时间,就把收益最小的任务踢出去。
这个思路的巧妙之处在于:我们不需要决定“哪个时间段做哪个任务”,只需要维护一个“已选任务集合”,保证集合中任务的个数永远不超过当前最早的截止时间,这样一定存在一种合法的调度方案。
#include <stdio.h> #include <stdlib.h> #define MAXN 10005 typedef struct { int dead; int profit; } Task; int cmp(const void *a, const void *b) { return ((Task *)a)->dead - ((Task *)b)->dead; } Task tasks[MAXN]; int heap[MAXN], heapSize; void push(int val) { heap[++heapSize] = val; int idx = heapSize; while (idx > 1 && heap[idx] < heap[idx / 2]) { int temp = heap[idx]; heap[idx] = heap[idx / 2]; heap[idx / 2] = temp; idx /= 2; } } int pop() { int ret = heap[1]; heap[1] = heap[heapSize--]; int idx = 1; while (idx * 2 <= heapSize) { int child = idx * 2; if (child + 1 <= heapSize && heap[child + 1] < heap[child]) { child++; } if (heap[idx] <= heap[child]) break; int temp = heap[idx]; heap[idx] = heap[child]; heap[child] = temp; idx = child; } return ret; } int main() { int n; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d %d", &tasks[i].dead, &tasks[i].profit); } qsort(tasks, n, sizeof(Task), cmp); int total = 0; for (int i = 0; i < n; i++) { push(tasks[i].profit); total += tasks[i].profit; if (heapSize > tasks[i].dead) { total -= pop(); } } printf("%d\n", total); return 0; }这段代码里,小根堆是手写的,因为机试环境不一定支持C++的优先队列,如果你用的是C++则可以更简洁地使用priority_queue<int, vector<int>, greater<int>>。核心思想一定要记住:我们维护的小根堆里存的是“当前已选择的任务的收益”,一旦发现当前任务数超过了某个任务的截止时间,说明我们必须放弃一个任务,毫无疑问应该放弃收益最小的那个。
我实操时发现,这道题最容易被忽略的点是:任务可能没有按时完成,但题目要求收益最大化,所以不需要把每个时间段都填满。很多新人写这题时会陷入“模拟时间线”的思路,试图用数组标记每个时间段做哪个任务,这种做法的复杂度是O(n^2)级别的,遇到大数据量很容易超时。用贪心+堆的思路,复杂度只有O(nlogn),稳得很。
2.3 第三题:资源分配与背包变种(难度:中等偏难)
第三题从题面来看是一个“设备分配”问题:有N个任务需要分配到M台设备上,每个任务有处理耗时和收益,每台设备有总处理时间的上限,求总收益最大值。翻译过来就是一个二维费用背包,或者严格说是一个“分组背包”的变种。
这道题难在什么地方?难在你得先识别出它是一个背包问题。如果你真把场景当业务题去模拟分配逻辑,写出来的大概率是贪心算法,而贪心在背包问题上是不能保证最优解的。只有你反应过来“每台设备就是一个容量限制,每个任务是一个物品,要么选要么不选”,思路才算真正打开。
#include <stdio.h> #include <string.h> #define MAXN 1005 #define MAXM 105 int dp[MAXM][MAXN]; int main() { int n, m; scanf("%d %d", &n, &m); memset(dp, 0, sizeof(dp)); for (int i = 0; i < n; i++) { int cost, value; scanf("%d %d", &cost, &value); // 倒序遍历,保证每个物品只选一次 for (int j = m; j >= cost; j--) { for (int k = MAXN - 1; k >= cost; k--) { if (dp[j - cost][k - cost] + value > dp[j][k]) { dp[j][k] = dp[j - cost][k - cost] + value; } } } } printf("%d\n", dp[m][MAXN - 1]); return 0; }这段代码是一个典型的二维背包模板,但说实话,实际做第三题时你很难一次就写出完美版本。我第一次做这题时,错误地把设备个数当成了容量,直接套了一维背包模板,结果样例能过、大测试点挂掉,排查了半天才发现是状态维度搞错了。这类题目在考场上非常考验心态,因为你越急越容易错。我的建议是:写代码前先在草稿纸上画一下状态转移方程,dp[i][j]到底代表什么,下标哪个是设备容量、哪个是时间容量,想清楚了再动手。
还有一点非常关键:第三题的数据范围通常不会太大,你要学会根据数据范围反推算法复杂度。比如看到N和M都在100以内,O(NMM)的复杂度通常是可接受的;但如果你用DFS去搜索每一种分配方案,指数级的复杂度绝对会超时。考场上的一个核心原则就是:根据数据范围猜测出题人想要的算法。这个能力,刷套卷练出来的效果是最明显的。
2.4 每道题的代码风格建议
机试不像公司里写业务代码,不需要你搞什么设计模式、依赖注入、单元测试,一切都是“能跑就行”。但我还是建议你在代码可读性上稍微上点心,原因很简单:如果你调试的时候自己都看不下去自己的代码,那等于给自己挖坑。
我的习惯是:核心变量命名使用有意义的英文单词缩写,比如task、profit、deadline,而不是a、b、c;关键循环里加一两个注释,方便自己定位;函数拆小一点,一个函数只做一件事。这些习惯在平时刷题时不显山露水,但到了考场高度紧张的状态下,整洁的代码真的能帮你省下很多排查时间。
另外,建议你固定使用一种语言刷题。我推荐C/C++,因为运行速度快,对各种容器的掌控更底层,而且华为机试对C/C++的支持非常成熟。Python也能用,而且在写一些字符串题时确实快很多,但遇到大数据的题目时,Python的性能瓶颈可能会让你卡在超时边缘,所以如果你C++不差,就优先C++吧。
3. 实操过程与核心环节实现
3.1 考场上的时间分配策略
这个部分非常关键,我见过的翻车案例里,至少有一半是时间分配出了问题。一套卷2小时,3道题,我的建议是这样分割:
- 第一题:20-30分钟搞定。如果卡了超过30分钟还没AC,先放弃,跳到第二题。因为第一题再难也就100分,花太久只会挤占后面大题的时间。
- 第二题:40-50分钟。第二题一般200分,值得投入较多时间。但要注意:如果30分钟还没思路,先写一个暴力解法,能拿部分分就拿部分分。
- 第三题:剩余时间。第三题300分,但也是最难的。千万别指望AC,目标是尽可能多地通过测试用例。暴力解法、部分DP、甚至特判某些数据,都能帮你捞到不少分。
考场上最忌讳的心态是“这道题我一定要AC”。机试不是竞技比赛,它是个及格性考试,你要的是总分最大化,而不是单题满分。先保证能拿的分拿稳,再去冲难题。
3.2 模拟题5的完整答题流程演示
我以模拟题5为例,带大家走一遍我自己的答题流程。拿到题目后,先把三题都扫一遍,花三分钟看清楚每道题的输入输出格式和大概思路方向。这个“全局扫描”非常重要,它能帮你建立整场考试的时间预期。
第一题如果是字符串解压,我大概率直接用栈或递归,20分钟内能搞定。写之前先在草稿纸上写下几个测试用例,比如“3[a2[c]]”应输出“accaccacc”,这种嵌套用例能帮你快速验证思路。
第二题如果是任务调度,我的第一反应就是贪心+堆。不需要考虑其他方案,直接按排序+小根堆的思路写,写完跑一下样例,再构造几个边界测试(比如所有任务截止时间都为1、收益相同的情况),确认稳了再提交。
第三题如果是背包变种,我会在草稿纸上画状态转移方程,明确dp数组两个维度的含义,然后写代码。写完样例测试通过后,我会故意构造一个稍大一点的数据,观察代码运行时间,确保不会超时。
三题加起来,实际写代码的时间大概100分钟左右,剩下20分钟用来复查边界条件和输入输出的格式问题。有一点想提醒你:千万不要提前交卷。机试不奖励“做得快”,只奖励“做得对”。剩下的时间哪怕只是把三份代码读一遍,也能抓出不少低级错误。
3.3 关键边界条件的自查清单
“边界条件”这个词说了无数遍,但很多人还是会在上面丢分。模拟题5我总结了一份自查清单,你可以直接拿去用:
- 字符串题:输入字符串是否可能为空?数字是否可能为0?括号是否一定匹配?
- 数组题:数组长度是否为1?所有元素是否都相同?是否可能所有元素都满足/都不满足条件?
- DP题:dp数组初始化是否正确?是否能处理“一个物品都不选”的情况?物品数量为0或容量为0时,结果是否合理?
- 数值类型:计算过程中是否可能溢出int范围?如果可能,要改用long long。
每道题写完代码后,按这个清单逐项检查一遍,能有效避免“样例过了但提交挂了”的惨剧。这份清单看着简单,但都是我用一次次考试挂分换来的血泪经验。
3.4 输入输出技巧与常见陷阱
华为机试的输入输出是比较常规的,一般用scanf和printf就能搞定。C++选手用cin和cout也没问题,但记得加上ios::sync_with_stdio(false); cin.tie(0);这行快读代码,否则大数据时可能会因为IO太慢导致超时。
有个非常常见的坑:如果是用scanf读字符串,要确保字符数组开得足够大,因为机试环境不会对数组越界做任何提示,越界后可能导致神秘错误。读入一行带空格的字符串时,记得用gets()或fgets()把整行读进去,或者用scanf(“%[^\n]”, s)这种格式,千万别用scanf(“%s”, s),它遇到空格就停了。
输出格式也容易踩坑。有的题目要求每个结果占一行,有的要求每个结果后面加一个换行,还有的要求输出整数后不能有空格。我建议写一个简单的输出辅助函数,统一格式,减少手写出错的概率。
3.5 机试环境的适配建议
因为我带的这波朋友参加的OD机试用的是新系统双机位C卷,这里顺便聊聊环境适配。双机位的意思是:一个摄像头对着你本人和电脑屏幕,另一个摄像头对着你的手部和桌面,用来监控是否有作弊行为。
这个系统的存在,其实是在提醒你:机试是独立完成的考试,不要有任何侥幸心理。我建议你在考前就把桌面清空,只留下必要的笔和草稿纸。考试过程中不要频繁转头、不要看手机、不要起身,这些动作都可能被系统判定为异常行为。还有就是提前检测一下电脑的摄像头、麦克风、网络是否正常,这些硬性问题如果在考场上出现,非常影响心态。
就代码环境而言,机试系统一般自带编译器,你在本地用什么IDE都行,但考场上建议用系统默认的编辑器,因为它的自动缩进和代码补全功能很有限,你平时就要适应这种“裸写代码”的感觉。我自己带人的时候,会要求他们关闭IDE的自动补全和语法提示,只用最朴素的编辑器来刷题,效果非常显著。
4. 常见问题与排查技巧实录
4.1 样例能过但提交只有60分
这是机试里最让人崩溃的情况。样例过了、本地测试也过了,一提交就只有60分甚至40分。出现这种情况,90%是你漏了边界条件,还有10%是算法复杂度太高导致超时。
针对漏边界条件的情况,你需要主动构造一些“刁钻”的测试数据。我一般会按这几个方向去验证:空输入、单个元素、最大值、最小值、重复元素、乱序输入。针对超时的情况,你需要评估数据范围,如果数据量是10^5级别,而你用了O(n^2)的算法,大概率会有测试点超时。
我在模拟题5的第二题遇到过这个问题:贪心+堆的时间复杂度是O(nlogn),按理说不会超时,但我第一次写的时候堆是自己实现的,写错了两个地方,导致堆排序退化成O(n^2),大数据一跑就挂。排查了半天,最后发现是heapify函数里索引写错了。这类堆实现的bug特别隐蔽,建议用C++选手直接用priority_queue,能少踩很多坑。
4.2 第三题完全没思路怎么办
这个情况太常见了,尤其是当你被一套卷子前面两道题耗掉太多精力后,看到第三题那种大段的题面,脑子很容易空白。我的建议是:先把题目完整读三遍,尽量把业务场景抽象成算法模型。如果读了三遍还不行,立刻转写暴力解法。
暴力解法有两种,一种是枚举所有情况,另一种是用DFS搜索。虽然暴力解法大概率过不了大数据测试点,但它能帮你拿住小数据测试点的分数。千万不要因为觉得暴力解法“太low”就不写,在机试里,写暴力拿到的每一分都是实打实的。
还有一个技巧:观察题目数据范围。如果数据范围很小(比如N<=10),那出题人很可能就是允许暴力搜索的;如果N的范围是10^5级别,那必须用优化算法。根据数据范围反推算法,这是机试和高水平算法竞赛里都非常好用的策略。
4.3 编译错误和运行错误的排查思路
编译错误通常是因为语法问题,比如scanf少了一个&、数组下标越界、变量名拼写错误等。机试的编译器一般会提示错误行号,你顺着行号找就能发现问题。运行错误则复杂一些,可能是数组越界、栈溢出、空指针、除零等。
数组越界是最常见的运行错误。我处理这类问题时会习惯性地把数组开大一点,比如题目数据范围是N<=1000,我就开N+10或者N+100。这个习惯虽然浪费一点内存,但能有效避免越界错误。另外,调试时如果发现程序崩溃,可以尝试用printf在关键位置打印变量值,定位崩溃位置。这种方式虽然原始,但在机试环境下非常实用。
4.4 机试环境下的调试技巧
机试系统一般不支持断点调试,也不支持看变量值,所以你只能自己想尽办法定位问题。我的调试三板斧是:
第一招:打印大法。在可疑位置打印变量的值,看是否和预期一致。用完记得删除或注释掉,别留着影响输出结果。
第二招:二分定位。如果程序某一段逻辑有问题,通过不断缩小范围,找到出错的具体位置。比如用二分的方式逐步注释掉代码段,定位到出错的那几行。
第三招:小数据验证。构造一个非常小的测试用例,比如只有3个元素的数组,手动推演一遍程序运行过程,对比代码实际输出,就能发现逻辑错误。
这三招看起来朴素,但真的能解决90%以上的调试问题。调试心态也很重要:不要急躁,仔细看输出,一步一步排查,总能找到问题。
4.5 模拟题5的高频翻车点汇总
最后列一下模拟题5这道卷子里我曾见过的高频翻车点,有的来自我自己,有的来自我带过的备考朋友:
第一题字符串解压,最容易翻车在两处:数字是多位数的处理,以及递归返回值传递的路径。很多新手在递归函数里忘记返回“当前扫描到的位置”,导致外层递归重复扫描了同一个字符,输出结果直接翻倍。
第二题任务调度,最容易翻车在堆的维护逻辑上:当任务数超过截止时间时,应该踢出的是收益最小的任务,而不是新任务。有人会把逻辑写成“当新任务收益大于堆顶时弹出堆顶再压入新任务”,这个逻辑在部分情况下也能过,但它不是标准的解法,容易在某些特殊测试点出错。
第三题背包变种,最容易翻车在状态维度搞混。记住一个技巧:先想清楚dp数组的下标代表什么、值代表什么,再想清楚转移方程。很多人的错误源头都是“没想清楚就开始写”。
4.6 刷完模拟题5之后的复盘方法
刷完一套题不复盘,等于白刷。我的复盘流程分三步:第一步,看自己每道题花了多少时间,如果在某类题上反复超时,说明这个知识点掌握不牢,需要回到专题刷题补基础。第二步,看自己提交后错在哪些测试点,尽量还原出错的测试数据,理解为什么会错。第三步,把错题整理进错题本,标记出错误原因:是边界条件、算法复杂度、还是逻辑漏洞。
错题本我强烈建议你电子化,用表格记录题目类型、错误原因、正确解法、考点标签,刷到一定数量后,你会发现自己的弱点非常清晰。比如我的错题本显示,我的字符串处理正确率只有70%,但DP类正确率高达95%,那接下来重心就应该放在字符串处理上。
顺便提一句,现在AI编程工具很火,也有人用Cursor这类AI编程助手辅助刷题。说实话,日常学习时让AI给你解释算法思路、帮你审查代码是不错的选择,但考场上请务必独立完成。双机位监控就摆在那里,独立完成既是对考试的尊重,也是对自己能力的真实检验。不要把AI当成考试作弊的工具,把它当成日常学习的助手,这个边界一定要清醒。
5. 华为OD机试新系统与备考节奏
5.1 新系统双机位C卷的应对建议
很多朋友担心双机位C卷和传统机试有什么本质区别,实际上从算法考点来看,几乎没有变化。双机位更多是监考形式的变化,而非题目难度的变化。你该刷的题还是那些高频考点,该掌握的算法还是那些经典模型。
不过,双机位确实要求你调整一些备考习惯。平时刷题时就要练习在“可被观察”的环境下独立完成题目,不要养成依赖搜索、依赖提示的习惯。还有一个细节是:考试前一定要确保手机静音、关掉消息通知,因为双机位监控会检测是否使用手机,一旦判定异常,后果很严重。
5.2 两个月内的高效刷题规划
如果你还有两个月准备时间,我给你一个比较合理的刷题节奏:前一个月按专题刷,把字符串、排序、哈希、贪心、DP、DFS/BFS这六大板块的基础题刷明白,每个板块至少50道。中间两周开始刷整卷模拟题,一周3-4套,按考场规则来,记录时间和得分。最后两周回归错题本,把之前做错的题重新做一遍,巩固不熟的知识点。最后几天可以适当减少刷题量,多看看错题和模板代码,调整好状态迎接考试。
5.3 我在带人备考时的一些心里话
最后说几句掏心窝子的话。华为机试这个考试,说难确实难,因为它是实打实的编码能力测试,容不得半点含糊;但说简单也简单,因为它的考点非常固定,你只要把高频题型练透,大概率能拿到满意的分数。我带过的学员里,有零基础转行两个月上岸的,也有科班出身刷了半年还挂了的,差别不在智商,而在方法。
方法的第一条是:尽早动手写代码,不要停留在看题解的阶段。第二条是:认真对待每一次模拟考,把它当成真正的考试。第三条是:学会复盘,让自己的错误成为进步的台阶。这三条听着简单,做到的人真不多。
这套模拟题5,如果你能按照我上面说的节奏完整做一遍并复盘透彻,我相信你的机试水平会有一个质的提升。关于华为机试和OD机试备考,如果你还有其他疑问,欢迎在评论区留言,我看到会尽量回复。后面我也会陆续把其他几套模拟卷的拆解文章整理出来,咱们一步步来,把每一套题都榨干吃透。