华为机试模拟题1:字符串压缩、任务调度与动态规划实战解析

发布时间:2026/9/1 9:07:58
华为机试模拟题1:字符串压缩、任务调度与动态规划实战解析 在准备华为机试这件事上我踩过的坑可能比大多数人刷过的题都多。华为机试这四个字对准备校招和OD岗位的朋友来说是绕不开的一道坎它考察的不只是算法能力更是你在有限时间内把题目抽象成代码的工程直觉。这套《华为机试编程模拟题1》是我按近期C卷的机考风格整理出来的题型和难度都对标真实考场不管你是刚入门的小白还是刷了几百道题想查漏补缺的老手都能从里面挖到点东西。模拟题1一共三道题分值分布参考了正式考试常见的100分加200分结构覆盖字符串处理、任务调度、动态规划三大高频考点。和网上流传的碎片化题目不同我把每道题的出题意图、边界条件、代码实现和失分点全部串起来讲尽量让你看完一道题就能举一反三而不是背一套答案就完事。接下来我们直接进入正题从整体设计思路开始拆。1. 华为机试模拟题1的整体设计与考点分布1.1 华为机试到底在考什么华为机试或者说华为OD上机考试核心是ACM模式下的算法编程不是让你写业务代码也不是考框架和中间件。你拿到的是一个题干、一组输入输出样例需要在牛客网这类OJ环境中手写完整代码从标准输入读取数据再把结果输出到标准输出。没有智能提示没有代码补全平时依赖IDE重构和自动导入的朋友第一次上考场会非常不适应。从分值结构看只要参考的是C卷和D卷的常见配置大多是一道100分题加两道200分题总分400或者500不同批次会有一点浮动。100分题通常是字符串、数组、模拟这类基础操作只要细心基本能拿下200分题开始上难度可能涉及栈、队列、贪心、图论、动态规划一道题就能拉开差距。模拟题1就是按这个模式设计的100分题给了一道字符串200分题两道分别是任务调度和动态规划整体节奏和真实考试非常接近。另一个大家常忽视的点是华为机试的环境限制比较多比如浏览器会限制切屏次数、代码编辑器不会自动保存、调试信息也可能被记录。正式考试还有双机位监考手机要架在侧面拍到手和屏幕。这些规则和算法能力无关但如果你不提前适应考试时一旦因为切屏被警告心态会炸得很厉害后面的题基本没法正常发挥。1.2 模拟题1的三道题设计逻辑这次的三道题分别是字符串压缩、服务启动时间计算、二维网格最小路径和。三种题型对应了三种不同的算法思维字符串压缩考的是对遍历和拼接的掌控属于“阅读理解题”大部分人能做对但想要满分就必须处理各种边界。服务启动时间计算是典型的图论题依赖关系建模成有向图本质上考拓扑排序和关键路径比普通的DFS、BFS要难一层。二维网格最小路径和是动态规划入门题很多朋友觉得DP很难其实难的不是递推公式而是初始化和边界的处理这道题就是最好的练习材料。三道题刚好覆盖了机试中最高频的考察方向。我在复盘近几年真题时发现不管是校招机试还是OD机试字符串处理和图论DP的出现频率非常高尤其是C卷基本每场都有类似的影子。把这套题吃透再去做其他模拟卷会顺手很多。2. 三道题的核心考点与出题人意图2.1 字符串压缩100分题为什么总是字符串100分题之所以偏爱字符串是因为它既能考察基本功又不会太难到劝退。字符串类题目通常不需要高深的算法但需要你非常熟悉语言内置的字符串方法比如Java里的StringBuilder、split、toCharArrayC里的string、getlinePython里的切片和join。很多考生不是不会做而是卡在API不熟白白浪费时间。模拟题1的第一题是“字符串压缩”。题目大概是这样给定一个只包含字母的字符串把连续出现的相同字母压缩成“字母出现次数”的形式如果压缩后的字符串长度不小于原字符串长度则返回原字符串。这个问题很经典但真正的坑点都在细节里从头到尾遍历时需要记录当前字符和计数遇到不同字符时把结果拼入StringBuilder最后还要把最后一组字符补上。漏掉最后一段几乎是所有人第一次写都会犯的错。出题人在这里真正想看的是你能不能在紧张状态下保持稳定的编码习惯。用一个curChar变量记录当前字符再配合count计数循环结束后再处理一次这就是标准解法。但很多人会想着在循环体内就把所有字符塞进结果导致边界判断逻辑混乱最后补丁越打越多。这种题最忌讳边写边想先理清几个状态再动手。2.2 任务调度与依赖关系图论在机试中的真实应用第二题是“服务启动时间”。题目大致是有N个服务每个服务有启动耗时某些服务依赖另外一些服务启动完成后才能启动给定依赖关系求启动完所有服务所需的最少时间。这个题贴近真实业务出题人其实是在模拟微服务部署或任务编排的场景非常符合华为对工程能力的偏好。这类题的本质是求有向无环图上的最长路径。每个服务的最早启动时间是从所有依赖它的前驱服务的完成时间中取最大值再加上自身的启动耗时。最终答案是所有服务完成时间的最大值。这里牵扯到两个关键点第一要能识别出这是一个图论问题而不是简单的数组遍历第二要会用拓扑排序或者记忆化DFS去处理依赖链条。很多考生卡在第二个点上是因为他们下意识用BFS或DFS去遍历图但每次访问一个节点时没有处理好“所有前驱都完成”这个条件。用拓扑排序可以优雅地解决每次删除入度为零的节点更新它的后继节点的最早启动时间直到队列为空。如果最后访问的节点数小于总节点数说明存在环那就要按题目要求输出特定异常值。这种题目平时多练一遍考场上就能省下至少二十分钟。2.3 动态规划300分题的本质是状态定义第三题是“二维网格最小路径和”其实是LeetCode 64的变体。给定一个m行n列的网格每个格子有一个非负数值从左上角走到右下角每次只能向右或者向下走求经过路径的最小和。这是典型的动态规划入门题但它出现在华为机试里仍然能刷掉一批人原因就是DP的边界初始化太容易出错。递推公式并不难dp[i][j] grid[i][j] min(dp[i-1][j], dp[i][j-1])表示走到当前格子的最小路径和等于当前格子的值加上从上方或左方过来的较小值。难点在于第一行和第一列的处理因为这两个方向上的格子没有“上方”或“左方”只能沿着边缘一路累加。如果初始化没做好后面所有计算都会错。这个题真正的价值是训练你要有“状态定义”的自觉。很多DP题不是算不出来而是不知道怎么定义状态、怎么把大问题拆成子问题。只要你有意识地从“最后一步”反推很多看起来吓人的题目都能快速找到思路。比如这道题最后一步一定是从上方或左方走到终点因此子问题就是走到上方格子和走到左方格子的最小路径和。这个思维方式比任何模板都值钱。3. 实操过程从读题到AC的完整题解3.1 第一题字符串压缩100分题目描述给定一个字符串将连续相同的字符压缩成“字符连续出现次数”的形式。例如aabcccccaaa压缩后是a2b1c5a3。如果压缩后的字符串没有变短则返回原字符串。这道题的输入输出格式很简单但在牛客网环境下还是要走标准IO。我用Java实现读取一行字符串然后开始遍历。import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); String s sc.nextLine(); sc.close(); if (s null || s.length() 0) { System.out.println(); return; } StringBuilder sb new StringBuilder(); char curChar s.charAt(0); int count 1; for (int i 1; i s.length(); i) { if (s.charAt(i) curChar) { count; } else { sb.append(curChar).append(count); curChar s.charAt(i); count 1; } } // 循环结束后补上最后一组字符 sb.append(curChar).append(count); String compressed sb.toString(); if (compressed.length() s.length()) { System.out.println(compressed); } else { System.out.println(s); } } }这个写法有几个细节值得注意。首先是循环结束后一定要再append一次这是我自己当年在笔试中丢过分的地方因为最后一组连续字符不会触发else分支如果不额外处理就会漏掉。其次是最后判断压缩后字符串是否变短时用小于号而不是小于等于为的是字符本身只有单个字母时压缩格式如a1反而更长应该保留原字符串。还有一点输出时直接换行即可OJ对行尾空格不敏感但最好不要自己加多余输出避免干扰判题。很多初学者会有一个疑问为什么不用HashMap统计字符总次数因为HashMap是全局统计没法区分“aabbbaa”中两个a段中间隔了b压缩结果应该是a2b3a2而不是a4b3。这就要靠遍历时保留当前字符状态而不是简单的计数。这也是这道题的灵魂。3.2 第二题服务启动时间200分题目描述输入两个整数N和M分别表示服务数量和依赖关系条数。接下来一行是N个整数表示每个服务的启动耗时。再接下来M行每行两个整数a b表示服务a启动完成后服务b才能启动。服务编号从0开始。求所有服务都启动完成所需的最少时间。如果存在循环依赖按题目要求输出-1或指定的错误码。拿到这道题第一步不是写代码而是要把依赖关系转化成有向图a是b的前驱那么存在一条从a指向b的边b的入度加一。每个服务的最早启动时间等于所有前驱的最早完成时间的最大值加上自己的耗时。最终答案是所有服务的最早完成时间的最大值。我推荐的写法是用邻接表存图加一个入度数组然后做拓扑排序。用一个队列维护所有入度为0的服务初始时把所有入度为0的节点入队将这些节点的最早启动时间设为自身耗时。每次弹出节点更新它的所有后继节点后继的启动时间 max(后继的启动时间, 当前节点启动时间 当前节点耗时 后继节点耗时中间值的最大情况。实现时更稳妥的是维护每个节点的最早启动时间start[i]初始化为0在拓扑排序的过程中不断更新最后答案取max(start[i]) duration[i]的最大值。用代码说话import java.util.*; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); int N sc.nextInt(); int M sc.nextInt(); int[] duration new int[N]; for (int i 0; i N; i) { duration[i] sc.nextInt(); } ListInteger[] graph new ArrayList[N]; for (int i 0; i N; i) { graph[i] new ArrayList(); } int[] indegree new int[N]; for (int i 0; i M; i) { int a sc.nextInt(); int b sc.nextInt(); graph[a].add(b); indegree[b]; } int[] start new int[N]; QueueInteger queue new LinkedList(); for (int i 0; i N; i) { if (indegree[i] 0) { queue.offer(i); } } int visited 0; int ans 0; while (!queue.isEmpty()) { int cur queue.poll(); visited; int curFinish start[cur] duration[cur]; ans Math.max(ans, curFinish); for (int next : graph[cur]) { // 后继节点必须等所有前驱都完成才能启动 start[next] Math.max(start[next], curFinish); indegree[next]--; if (indegree[next] 0) { queue.offer(next); } } } if (visited ! N) { System.out.println(-1); // 存在环按题目要求处理 } else { System.out.println(ans); } } }这个题有三个坑。第一个坑是更新后继节点时要用取最大值而不是直接赋值。因为一个节点可能有多个前驱必须等最慢的那个前驱完成才能启动直接赋值会丢失前一个前驱的信息。第二个坑是最终答案不是最后一个出队的节点的完成时间而是所有节点完成时间的最大值因为不同节点可能并行启动最后一个启动的服务不一定最晚完成。第三个坑是环的判断如果visited不等于N说明有环拓扑排序无法遍历全部节点这就要按题目要求输出异常值。3.3 第三题二维网格最小路径和300分题目描述给定一个m行n列的二维数组grid每个元素是非负整数。从左上角grid[0][0]出发每次只能向右或向下移动到达右下角grid[m-1][n-1]求路径上所有数字之和的最小值。这题最直观的解法是用二维dp数组但进阶一点可以直接在原数组上原地修改节省空间。我习惯开一个dp数组保持原数据不动虽然多了点空间但调试时更方便。核心逻辑如下import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); int m sc.nextInt(); int n sc.nextInt(); int[][] grid new int[m][n]; for (int i 0; i m; i) { for (int j 0; j n; j) { grid[i][j] sc.nextInt(); } } int[][] dp new int[m][n]; dp[0][0] grid[0][0]; // 初始化第一行只能从左往右走 for (int j 1; j n; j) { dp[0][j] dp[0][j - 1] grid[0][j]; } // 初始化第一列只能从上往下走 for (int i 1; i m; i) { dp[i][0] dp[i - 1][0] grid[i][0]; } // 递推 for (int i 1; i m; i) { for (int j 1; j n; j) { dp[i][j] grid[i][j] Math.min(dp[i - 1][j], dp[i][j - 1]); } } System.out.println(dp[m - 1][n - 1]); } }这道题最大的陷阱在输入格式上。m和n通常从第一行读取网格数据按行输入所以Scanner读取时要特别注意不要多读或者少读。代码本身逻辑不复杂但我见过太多人卡在“忘记初始化第一行第一列”这个问题上。第一行只能从左往右累加第一列只能从上往下累加这两部分没有选择余地因此必须单独处理。如果你跳过了这一步后面的dp[i][j]会用到dp[-1][j]或dp[i][-1]直接数组越界或者得到错误结果。另外还有一个优化点如果不允许使用额外二维数组可以原地修改grid让grid[i][j]变成从起点到该点的最小路径和。这样空间复杂度降到O(1)。但机试判分只看结果用哪种写法都行关键是写完要自测一次题目给的样例再补一个m1或n1的边界case确保不会越界。4. 机考实战输入输出、双机位与失分点总结4.1 ACM模式下的输入输出怎么处理华为机试采用ACM模式就是你要自己写Scanner或者BufferReader读数据System.out.print输出结果不像是力扣那样只填函数体。很多人第一次接触这个模式会非常不适应尤其是长时间用IDE自动提示的人连“import java.util.*”都要想一下。这里我强烈建议提前熟悉牛客网的在线OJ环境每天至少用ACM模式练两三道题把Scanner的nextInt、nextLine混用问题彻底搞清楚。nextInt和nextLine混用是高频翻车点。比如先读一个整数N再读一行字符串如果直接用nextLine会发现读到的字符串是空的这是因为nextInt结束后缓冲区里还有一个换行符nextLine会直接把这个换行符消费掉。解法是在nextInt之后额外调用一次nextLine来吞掉换行或者全部用nextLine然后手动parse成整数。这个坑在考试中很容易被忽略但一旦踩了整个题目的输入都会错位。另外要特别注意读入的行数不一致问题。有些题目会包含多组测试数据你可能需要用while (sc.hasNext())循环读有些题目只有一组数据循环读会导致无法退出。做题之前先看题目描述确认是单组还是多组再决定要不要用循环包裹。4.2 双机位C卷的考试纪律与备考策略现在很多华为机试场次采用双机位监考电脑摄像头拍正面手机架在侧面拍手部动作。这个不是吓唬你最近确实有朋友因为考试时低头看手机被判定作弊成绩直接作废。建议考试前准备好手机支架把手机调整到能看到手和屏幕的位置然后整场考试完全不要碰手机。哪怕是手机来了电话或者消息也不要拿起来忍到考完再说。C卷的题目范围相对固定很多朋友在搜“C卷真题题库”但我不建议死记硬背题目因为题目表述可能会改输入输出也有可能变形。真正有效的策略是刷题时把每一类题的核心算法记牢比如字符串双指针、拓扑排序模板、DP状态转移模板考试时在模板基础上套用。只背答案的风险很大一道题换个参数、加个条件背下来的代码就废了。考试时间分配也很重要。我自己的策略是先花三分钟把三道题都看一遍估计难度。一般先做100分题确保拿分再做200分题里自己更有把握的一道最后留二十分钟做剩下的一道。如果卡了十分钟还没有完整思路果断写个暴力解拿部分分不要死磕。华为机试是按通过用例比例给分的暴力解能过一些简单case分数不会太差。4.3 提交代码前必看的检查清单这里把我每次提交前都会快速过一遍的清单分享出来基本都是血泪教训检查点常见问题应对方法输入读取nextInt和nextLine混用导致空串统一使用BufferReader或者补充吃换行数组越界第一行第一列初始化遗漏单独处理二维DP的边界部分数据类型结果超过int范围使用long尤其是涉及累加的题目循环终止多组/单组读取逻辑写错先确认题目是单组还是多组测试输出格式多输出空格、少输出换行严格按题目要求不自行发挥特殊情况N为0、字符串为空、图存在环逐一对极端场景写if判断还有一个小技巧如果题目允许提交前在本地跑一下样例之外的自测case比如一个小输入、一个大输入。大输入主要为了验证是否会超时或者内存溢出小输入则是验证边界逻辑。OJ虽然不会告诉你每个case通过多少但你可以提前模拟判题过程排出低级失误。5. 备考节奏一套题的正确打开方式5.1 刷题时间和强度怎么安排如果你离考试还有两周时间不算充裕但完全来得及。建议前三天先做分类专项比如字符串一天、数组哈希一天、图论DP一天把每种题型的模板过一遍。接下来五天每天完整做一套模拟题按考试时间限时就当作正式考试来对待。最后三天回归错题把之前做错的题重写一遍尤其是因为边界条件丢分的题。最后一天不做新题只复习模板笔记和易错清单保证手感和心态。如果你复习时间只有一个周末那就不要贪多。顶多把模拟题1的两道简单题做透再把任务调度这类高频题型的模板背下来。机试的通过率很大程度取决于稳定发挥与其做十道题走马观花不如把三道题写成标准答案确保遇到类似题能快速AC。5.2 复盘比刷题更重要我刷题有一个习惯每道题AC之后还会再想一遍有没有更优解。比如最小路径和用二维dp能过但能不能用一维数组滚动更新服务启动时间用拓扑排序能过但能不能用记忆化DFS如果时间允许把这些优化写法也实现一遍理解会更深入考场上遇到变形题也能快速迁移。更关键的是把错题记录成笔记不要只记代码要记“为什么错”。我遇到过最多的错题原因不是算法不会而是看错了输入格式、漏了初始化、多写了空格。这些错误在考场上同样会犯所以每一次复盘都是消除考场失分点。把每道题涉及的关键易错点写在一张纸上考前扫一眼效果比临时百度强太多。5.3 关于模拟题1的使用建议这套模拟题1我是按真实考场的节奏设计的所以建议你给自己留出完整的两个小时中间不要查资料、不要中断甚至可以把手机放远一点模拟双机位监考的感觉。做完之后对照题解逐题分析不要只看对错要看看自己的代码和标准解法的差别在哪里哪些地方是算法缺陷、哪些只是风格不同。说实话机试考的不只是你会不会某一道题而是你在压力之下能不能保持稳定的代码质量。平时练题时养成好习惯比如变量命名清晰、每个循环都检查边界、提交前自测考场上才能从容一些。这套模拟题1如果能在规定时间内做完并且三道题全部AC那面对正式题目的信心会足很多。最后再分享一个小技巧我每次做这类机试模拟题时都会把自己代入真实的考试场景连IDE的字体都调成考场环境差不多的样式先把环境适配做好再谈刷题。上面这些题目和思路都是我经过反复验证的尤其是任务调度那道题建议你亲手把邻接表写一遍不要只是看懂代码因为考场上能写出来和能看懂完全是两码事。