聊起华为2021届实习机试题,我最深的感触不是某道题难得离谱,而是很多同学明明刷题量不少,却栽在最基础的地方:题目读懂了,代码也写出来了,一跑发现输入格式没处理对,或者边界case直接让程序崩了。机试不是平时在IDE里跑通几个样例就算完事,它有一套自己的规则和判分逻辑,摸不清楚的人,代码写得再好也可能只拿一个安慰分。
我打算从2021届实习机试的整体情况出发,把考试规则、题型分布、代码细节和复习路线完整过一遍。这篇文章适合两类人:一类是刚开始准备实习机试、还不太清楚ACM模式怎么写的大三学生;另一类是已经刷了不少题,但总在机试环节翻车的同学。前一类可以参考备考节奏,后一类可以重点看看输入输出和边界处理这部分的避坑经验。
1. 机试现场的硬规则:时长、编译环境、判分逻辑,每一条都影响最终结果
1.1 考试时长与题量设计
华为2021届实习机试的总体安排,基本延续了历年在线笔试的常见设置:时长两小时,题目数量三道,难度从简单到困难逐题递增。第一题通常属于“送分题”,主要考查基础语法和简单逻辑;第二题开始加入一些数据结构和基础算法设计;第三题则是拉开差距的压轴题,往往涉及动态规划、图搜索或者复杂的模拟过程。
大多数同学的真实体感是:第一题10到15分钟搞定,第二题花40分钟左右,第三题看缘分,整体节奏还算紧凑。但这里有个容易忽略的点——两小时是三道题的合计时间,不是每道题独立计时。很多人习惯先死磕第三题,结果没留够时间检查前两题的边界case,最后第一题因为一个数组越界或者空字符串处理失误被扣掉大半分数。
所以我的建议很直白:从第一题开始按顺序做,每做完一道就花两三分钟检查这四件事——输入是否读完整、边界值(空、最小、最大)是否覆盖、输出格式是否有空格或换行差异、有没有死循环风险。确认没问题再进入下一题,宁可在前面几道题上多花五分钟做防御性检查,也不要抱着侥幸心理直接提交。
1.2 在线判题平台与ACM模式
这类机试通常在在线判题平台上进行,采用ACM竞赛模式,也就是大家常说的“ACM模式”。它和力扣那种核心代码模式有本质区别:力扣帮你封装好了输入输出函数,你只需要实现一个方法;ACM模式则需要你自己读取标准输入、自己解析数据、自己把结果打印到标准输出。
具体来说,题目会给出输入描述和输出描述。输入描述规定数据从标准输入(stdin)读取,可能是多行、多组数据,也可能有特殊分隔符;输出描述规定结果必须按照指定格式写入标准输出(stdout)。平台会对输出做严格比对,多一个空格、少一个换行、多输出了调试信息,都会导致判题结果为答案错误。
我见过太多同学在本地IDE里写“print(123)”能出结果,但放到机试环境却各种失败,原因就是没有在代码里处理输入。比如第一题要求读入一个整数n,然后读取一行n个整数,正确写法是在代码里用标准输入读取,而不能在代码里写死某个测试数据。这种错误属于“没入门”级别,但在机试中出现的频率远超想象。
1.3 判分逻辑与部分得分策略
机试判题一般是逐测试点判定。题目通常包含若干个测试用例,包括样例和隐藏用例,全部通过得满分,部分通过得部分分。具体分值比重不会提前公布,但有一个基本规律:样例通常只覆盖最简单的情况,隐藏用例才是得分大头。
举例来说,一道第一题可能有8到10个测试点,前两个测试点是题面给出的样例,剩余的是隐藏输入。如果你只处理了正常情况,没有处理空输入、极大极小值、特殊字符这些边界条件,那大概率会挂在隐藏用例上。隐藏用例设计的目标就是“坑”那些思路正确但细节粗糙的代码。
因此,得分策略应当是把重心放在“正确率”而非“题目的绝对难度”上。尤其面对第三题的时候,如果一时半会想不出完全正确的解法,不要直接放弃,可以用暴力枚举写一个复杂度较高的版本,只处理小规模数据,争取过掉部分测试点。在实习机试中,能拿到的每一分都直接关系后续面试的推进,宁可一开始用笨办法拿个几十分,也不要交白卷。
2. 高频考点梳理:三道题背后的能力考察逻辑
2.1 第一题的真实目标:读题能力与基本编码功底
第一题看起来简单,它真正考的不是算法,而是你有没有把题目描述完整读进去。2021届机试中出现频率较高的第一题类型包括字符串处理、数组操作、基础数学计算,以及一些简单的逻辑判断。
比如“给定一个字符串,统计其中满足某种条件的字符个数”,这类题本身的算法含量约等于零,但存在几个明显陷阱:字符串中可能包含空格,输入可能是一整行带空格的字符串;字符范围可能是大写字母、小写字母、数字甚至中文;题面可能会强调“区分大小写”或“忽略大小写”。这些细节直接决定你写的条件判断是否正确。
我的建议是,第一题不要追求“快”,而是追求“稳”。哪怕是五分钟就能写完的简单题,也要花一分钟把题目描述从头到尾再看一遍,特别是输入描述和输出示例。很多扣分不是因为不会写,而是因为题目说“以空格分隔输出”,你按换行分隔输出,平台直接判错。
2.2 第二题的常见难度点:复杂模拟与基础动态规划
第二题通常介于中等难度,常见题型有几类:复杂逻辑模拟(比如多步骤操作、状态随输入变化的规则模拟)、简单动态规划(背包、最长子序列等)、二分查找变体,以及一些数据结构基础题(栈、队列、哈希表的综合运用)。
这里我最想说的一点是,复杂模拟题不一定比DP好写。模拟题往往代码量很大,涉及的判断分支很多,写着写着就容易各种逻辑遗漏。有些同学看到题目描述里有一堆规则就头痛,实际上这类题的得分率比DP还低,因为规则太多,很容易在某个角落漏掉一个if判断。
应对复杂模拟题,我习惯在动手写代码之前先在草稿纸上把规则拆成一条条清晰的步骤。不用写形式化的伪代码,就是像写购物清单一样,把“步骤A之后做什么”“什么条件下执行步骤B”列出来。这能大大降低编码时的bug率。第二题的核心价值在于考查“把现实规则转换成程序逻辑”的能力,而不是背了多少算法模板。
2.3 第三题的考察方向:状态搜索与优化意识
第三题是拉开差距的题,考察方向集中在:树与图的遍历、搜索剪枝、记忆化搜索、区间动态规划、状态压缩、单调栈/队列优化等。这类题往往题意不直白,会包装成一个看起来挺复杂的业务场景,比如“物资调度”“路径规划”“任务分配”,剥开外壳之后才能看到内核。
第三题还有一个显著特征:题目的数据范围提示会直接影响算法选择。举例来说,n≤20时,常考虑搜索或状态压缩;n≤2000时,通常需要O(n²)的DP可行;n≤10⁵时,一般需要O(n log n)或者O(n)的解法。很多人看到题目之后不先看数据范围就直接写,写了半天才发现自己的O(n²)算法根本无法通过大数据量测试点,既浪费了时间又打击自信。
如果第三题短期内没有思路,不如把前两题重新检查一遍,确保得分稳定后再回头处理。第三题哪怕只写出暴力版本,也能拿一部分测试点的分数,这比最后一刻交一个编译错误或者运行时崩溃的空壳要强得多。
3. 实战推演:三类典型真题场景的读题、设计、编码全过程
3.1 场景一:带复杂规则的字符串变换
这类题几乎每年都会出现。题目可能会说:给定一个只包含字母的字符串,按照某种规则进行压缩,例如连续三个相同字母可以合并为下一个字母,如果合并后又形成新的连续相同字母,则继续合并,直到无法合并为止。
拿到这种题,第一步不是直接写代码,而是先把规则吃透。以这个例子来说,关键是“合并之后是否触发连锁反应”。这决定了你是用简单的单次遍历扫描,还是需要用栈来维护动态状态。
栈的解法很直观:遍历字符串中的每个字符,与栈顶元素比较,如果相等就计数加一;当计数达到三时,执行合并操作并检查新的元素是否与次栈顶相等。整个过程只需要一遍扫描,复杂度O(n)。
其实这类模拟场景背后考的就是“能不能想到用栈来维护连续状态”。很多人会用反复删除字符串的方法,每删除一次就重新扫描一遍,遇到极端输入会退化成O(n²),甚至O(n³)。这就是典型的“思路当场能想到,但性能不过关”的写法。机试判题并不会因为你“思路简单”就手下留情,超时就是超时。
3.2 场景二:任务分配类动态规划
作为第二或第三题的高频形态,任务分配题往往是这样:有若干任务,每个任务有执行时间和收益,给定一个总时间上限,求最大收益。这几乎就是经典的0-1背包问题,但题目会加很多业务包装,比如“任务的先后顺序有依赖关系”“部分任务必须连续执行”“收益在过程中会衰减”。
这个场景的思考链路是:
- 先判断状态变量是什么。通常需要两个维度:已处理的任务数量和当前消耗的总时间。
- 考虑是否需要对任务排序。如果任务本身顺序固定,就不需要排序;如果依赖顺序,可能需要先按某种规则排序再DP。
- 设计转移方程。第i个任务不选择时,状态不变;选择时,由前一个状态加上收益。
这类题真正的坑往往藏在“依赖关系”里。比如任务B依赖任务A,那么就不能简单地按背包思路处理,而要增加“前置状态”判断。有些考生习惯把DP状态数组列出来就上线写转移,结果漏掉了“依赖”这个条件,样例跑通了,隐藏用例全挂。正确做法是先把依赖图画出来,确认是无环结构后再考虑拓扑排序+DP的方案。
3.3 场景三:网格路径问题
网格路径题也是机试的常客。常见形态为:在一个M×N的网格上,有些格子可通行,有些格子有障碍,问从起点到终点的最短路径,或者满足某些条件的不同路径总数。
求最短路径首选BFS,复杂度O(M×N),一般不会超时。但很多题目会额外增加限制条件,比如“路径中最多只能经过两个障碍物”“某些格子只能在特定时间开启”“存在权值消耗”。增加这些条件之后,BFS的状态就不只是坐标,而是坐标+附加状态。
一个常见的处理办法是扩展状态维度。例如“最多只能经过k个障碍物”的问题,可以用三维数组dist[i][j][t]表示到达(i,j)且已经越过t个障碍物的最短步数。BFS的每个节点都存(x, y, t),当走到障碍格且t<k时才允许进入障碍格。仔细理解这一步,第三题就有希望拿到不错的部分分。很多人想不到扩展状态,一味把问题往DFS+剪枝方向想,写出来的代码既长又慢,实际得分反而不如扩展BFS高。
4. 影响最终得分的编码细节:输入解析、边界条件与复杂度陷阱
4.1 ACM模式下的数据读取与解析
ACM模式要求自己处理标准输入,这一步看似基础,出问题的频率却相当高。常见的输入格式大致分为几类:
| 输入格式 | 示例 | 处理方式 |
|---|---|---|
| 换行分隔的单个数 | 第一行为n,后续n行每行一个数字 | 用循环逐行读取 |
| 行内空格分隔 | 每行n个整数,共m行 | 按行读取后按空格拆分 |
| 逗号或其他字符分隔 | 数据形如“1,2,3,4,5” | 先读取整行,再按分隔符拆分 |
| 未知数量的多组数据 | 持续读取直到EOF | 使用 while(cin>>x) 或类似机制 |
用C++编写时,区分cin和getline要格外小心。如果先读取一个整数,再读取一行带空格的字符串,cin读取整数后会在输入流中留下一个换行符,直接使用getline就会读到一个空行,导致后续解析出错。常见处理办法是在cin操作后加一行“忽略剩余换行”的语句,或者在读取字符串时统一用getline。
Python相对友好一些,使用input()逐行读取,处理空格分隔可以使用split(),但要注意input()遇到EOF会抛异常,需要用try/except或使用sys.stdin批量读取的方式处理多组数据。如果输入规模较大,建议使用sys.stdin.buffer.read()一次性读取所有数据再统一解析,这样也能加速某些大数据场景下的程序运行。
4.2 边界条件:什么情况会让代码当场崩溃
| 边界输入 | 可能导致的错误 | 提前处理办法 |
|---|---|---|
| 空字符串/空数组 | 访问下标越界 | 先判空再访问 |
| 只有一个元素 | 循环处理时直接错过或越界 | 长度为1的单测 |
| 最大值输入 | int溢出 | 使用long long类型 |
| 极限输入长度 | 算法超时或内存超限 | 提前估算复杂度 |
| 输入数据包含空格 | 按行读取后解析错误 | 使用split或字符串流处理 |
很多人自己在本地测试的时候,用的都是题面样例,从来没测过空数组或者单个字符的情况。可隐藏测试点偏偏就喜欢这类“极端且合理”的输入。一个最简单的防御性习惯是:写完代码不要立刻提交,先花一分钟构造几个极端输入手动跑一遍。比如统计字符串中某字符数量,就试试空字符串;处理数组求和,就试试数组长度为1的情况;涉及除法,就试试除数为0的情况。这一分钟能避免很多分数意外蒸发。
4.3 运行超时与内存超限的常见根源
超时是机试中非常常见的考生杀手。导致超时的原因主要有几类:算法复杂度本身太高、数据结构使用不当、代码中有不必要的重复计算,以及因为不会读题而有循环条件写错导致死循环。
举一个最简单的例子:在数组或字符串中频繁使用“删除某个元素”操作。如果你用C++的vector的erase,或者Python的list的pop(i),操作本身是O(n)的。在循环里反复删除,整体复杂度就可能变成O(n²)甚至O(n³)。本地测试数据太小看不出来,平台的大数据测试点一跑就超时。正确思路是,需要频繁删除的场景尽量用链表结构,链表的删除操作是O(1);如果不是必须原地修改,也可以选择用新数组保存符合条件的元素,用空间换时间。
内存超限相对较少见,但一旦出现就很彻底。常见原因包括:三维数组开得过大、递归没有设置终止条件导致栈溢出、DP数组无意义地加大维度。写代码之前先算一下内存:一个int是4字节,一个int[1000][1000]大约是4MB,看起来不大,但如果是int[1005][1005][1005],直接就是4GB,必然内存超限。估算内存是基本功,不能等到平台报错了才懊恼。
4.4 输出格式:不要带着多余的调试信息提交
这一条听起来像废话,但在真实机试里几乎每场都有同学因为输出多了调试内容而丢分。比如写代码时用了print/printf来打印中间变量,提交前忘记删掉,结果最终输出除了正确结果之外还有一堆乱七八糟的数值,平台直接判定答案错误。
输出相关的其他毛病包括:结果要求保留两位小数,你输出的是浮点数默认格式;要求“每个结果占一行”,你输出结果之间用空格分隔;要求“末尾无多余空格”,你却习惯性多打了一个空格。这些细节在判题的时候都是实打实的扣分项,没有“酌情处理”这一说。
我给一个比较稳妥的自检方法:写完代码后,检查所有print/printf语句是否都是最终结果输出,所有与调试相关的输出全部删除或注释掉。如果是输出数组,先在草稿纸上写下输出格式,再对照代码检查一遍循环边界、分隔符和换行符,确保和题目要求完全一致。
5. 从零开始备战:三周训练计划与提分效率建议
5.1 第一周:适应ACM模式与基础语法加固
第一周的重点不是刷难题,而是全面适应“在线判题系统”的代码书写方式。你可以找一个在线评测平台,把过去做过的一些简单题改用标准输入输出重新写一遍,目标是做到面对任何一道简单题,都能在10分钟内完成“读题-写代码-调试-提交”的全流程。
这一阶段建议集中练习:字符串处理(统计、翻转、去重、替换)、数组操作(排序、反转、区间查找)、基础数学(最大公约数、素数判断、进制转换)。这些知识本身不难,但它们构成了绝大多数机试中第一题和第二题的底层支撑。
另一个任务是把C++或Python常用的输入输出模式整理成一个自己的笔记。比如C++里scanf与cin各自的优缺点、Python里stdin的批量读取方法、字符串split与数值转换的写法。这样到了正式机试的时候,不需要现场翻语法,直接凭肌肉记忆就能写对输入解析代码。
5.2 第二周:按题型专项突破,建立“题感”
第二周从杂题练习转向分题型训练。建议按这几个方向来安排:栈与队列应用、哈希表与计数、搜索(BFS/DFS)、动态规划、贪心算法、二分法。
不必平均用力。对于目标只是扎实通过机试的同学,栈、哈希表、搜索、简单DP和二分需要重点掌握;贪心在很多题里属于锦上添花,不必花太多时间研究复杂的贪心证明。每天练习的题量不必多,5到8题足够,但每道题做完之后一定要复盘:这一题属于什么考点?我的思考路径是什么?哪个环节拖慢了速度?
这个阶段最重要的目标不是刷题数量,而是形成“读题之后迅速归类”的直觉。看见字符串操作想到栈或哈希表,看见最短路径想到BFS,看见最优分配想到DP。一旦直觉形成了,你面对第三题的时候心态会稳很多。
5.3 第三周:限时模拟、弱项突破与状态调整
第三周开始实战模拟。找两三套难度和风格接近的模拟题,用完整的两小时时间从头到尾做一遍。模拟的意义在于暴露真实环境下可能出现的问题:平时练习可以中途翻资料,但真正考试时你能依赖的只有自己的大脑;平时可以随便调试很久,但真正的考场时间非常紧张。
建议每场模拟结束之后做一份错题日志,内容包括:这道题为什么做错、当时的思路卡在哪一步、正确解法是什么、下次遇到类似题型应该注意什么。这份日志比再刷二十道题都要宝贵。
如果模拟过程中发现某个考点连续出错,比如每次遇到动态规划就思路卡顿,那就走专题回顾路线,把DP的核心模型(背包、LIS、区间、状态压缩)重新过一遍,配合针对性练习解决掉。带着明显的知识漏洞上考场,和大白天出门忘了穿鞋没有本质区别。
6. 一些考场上才用得到的实操心法
最后分享几个我实际使用过的考场小技巧,听起来都很基础,但回头看总能帮到不少人。
第一,先花3分钟通读三道题目,再决定做题顺序。正常情况下按题号顺序做,但如果第三题看起来特别眼熟,脑子里有思路了,可以先把思路简要写下来再回头做前两题,避免过一会儿遗忘。当然,如果第三题思路不完整,就不要在看完题后立刻深陷进去,免得前两题时间被挤占。
第二,把“保存”和“提交”分清。有些平台可以先保存再统一提交,查错阶段尽量使用保存功能,等代码稳定后再提交。频繁提交不仅浪费时间,也会影响心理状态。
第三,如果遇到编译错误,优先检查语法环境的选择是否正确。比如题目支持C++17,但平台默认C++11,某些高级语法特性就可能编译失败。选择正确的语言标准版本,再检查有没有语法拼写问题,往往能快速解决。
第四,不要在一道题上连续卡超过30分钟左右还毫无进展。这时候情绪已经开始消耗你的判断力了,果断切题,哪怕做点简单的检查也是一种“重启动策略”。很多时候冷静下来再回头看,刚才那题的错误一眼就能发现。
机试说到底是一场“规则明确、边界清晰”的能力测试,它不像面试那样有临场发挥的空间。你能做的,就是提前把所有变量都控制住:读题严谨一点,代码稳健一点,检查细致一点。2021届的机试已经成为过去,但那些题目暴露出的问题,每一届都在重复上演。希望这篇复盘能帮你避开那些已经反复出现过的坑,少丢那些本不该丢的分。