ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

华为机试模拟题7复盘:字符串、滑动窗口、动态规划与任务调度实战解析

华为机试模拟题7复盘:字符串、滑动窗口、动态规划与任务调度实战解析 前几天我把手头那套《华为机试编程模拟题7》从头到尾限时撸了一遍两个半小时四道题全程盯着屏幕像在打仗。做完之后最强烈的感受就一个机试这东西和平时刷题真的是两码事。平时你在 LeetCode 上慢慢想没人催你到了机试页面里时间一滴滴走输入输出的格式问题、结构体的堆法、边界条件的判断全部压在同一段时间里稍微一慌就全乱套。这篇文章不聊虚的就把我这套模拟题7的完整复盘写出来。四道题分别覆盖了字符串处理、滑动窗口、动态规划和流程模拟都是机试里的高频方向。我会把每道题的读题思路、代码实现、易错点都拆开讲后面再聊一聊不同基础怎么安排刷题路线以及我踩过的坑。如果你也在准备华为机试或者类似的招聘机试这篇文章应该能帮你少走不少弯路。1. 华为机试到底在考什么规则与核心考点1.1 考试规则和评分机制先说清楚规则不然复习方向容易跑偏。不同批次、不同岗位的机试安排会有些差异但大体框架是稳定的时长一般在一个半小时到两个半小时之间题目数量通常是三到四道难度分布整体上前易后难。很多人第一次考的时候没概念以为像学校考试一样按点给分其实机试的评分机制普遍是按测试用例的通过比例给分。这个机制带来的直接结论是哪怕一道题你没法拿到满分只要把暴力版本写出来能过多少用例就捞多少分。我在模拟题7的第三题里就亲身体会到了这一点——动态规划想不出优化解法先把 O(n^2) 的版本写上至少能覆盖掉大部分常规用例。所以备考阶段就要养成一个习惯永远不要留白暴力解也是分。关于语言选择机试一般支持 C/C、Java、Python、Go 这些主流语言。我的建议是优先选自己最熟的但如果你水平都差不多Python 在编码速度上确实有优势尤其字符串处理和模拟类题目写起来比 C 短不少。不过用 Python 要注意运行效率数据范围到 10^5 级别纯 Python 的 O(n^2) 算法很容易超时得提前想好替代方案。另外Python 的递归深度默认只有 1000涉及 DFS 的题目要么改迭代要么在开头加 sys.setrecursionlimit这个细节我后面还会细说。1.2 核心考点分布与选择思路我自己把网上能看到的华为机试题目和各类模拟题做了个粗略统计按出现频率大致排了个序考点方向出现频率典型题型字符串处理很高压缩解压、去重排序、子串匹配数组 / 双指针很高滑动窗口、快慢指针、区间合并排序 / 贪心高任务调度、区间问题、最优化分配动态规划高背包、最长子序列、编辑距离流程模拟中高状态机、指令解析、时间线模拟DFS / BFS中岛屿数量、迷宫最短路径图论低最短路、拓扑排序为什么是这几个方向因为机试不是奥赛它考察的是工作里真正会用到的基础编码能力。字符串处理贴近日常开发双指针和滑动窗口是基础算法思维动态规划考察的是状态设计能力流程模拟则完全在模拟真实业务里那种按照规则一步步推进的场景。华为机试很少出偏难怪题把常规题型的套路练透比钻研冷门算法有用得多。这套模拟题7的安排就很典型第一题字符串解压缩、第二题最长无重复子串、第三题最长递增子序列、第四题任务调度。四道题对应四个方向难度逐步爬升基本就是机试出题的常规配方。2. 模拟题7核心真题思路拆解2.1 第一题·字符串解压缩套路题里的细节陷阱2.1.1 题目描述与题意分析题目给一个经过压缩的字符串比如3[a2[bc]]要求还原成abcbcabcbcabcbc。规则是数字[字符串]表示中括号里的内容重复数字次中括号支持嵌套。输入只包含数字、小写字母和方括号输出解压后的完整字符串。这题第一眼看过去就是典型的栈应用场景。为什么想到栈因为嵌套结构天然就是后进先出——最内层的中括号要先处理完再把结果交给外层。这种结构用递归也能解但栈更直观而且不用担心递归深度问题。机试环境里能用迭代替代递归的就别用递归减少不可控因素。2.1.2 代码实现与关键细节我写的是 Python 版本def decode(s: str) - str: stack [] # 栈中元素为 (之前的字符串, 重复次数) cur # 当前累计的字符串 num 0 # 当前累计的数字 for ch in s: if ch.isdigit(): num num * 10 int(ch) elif ch [: stack.append((cur, num)) cur num 0 elif ch ]: prev, repeat stack.pop() cur prev cur * repeat else: cur ch return cur s input().strip() print(decode(s))这里有个特别容易踩的坑数字可能是多位数。比如12[a]如果你一看到数字就直接处理不累加很可能只重复 2 次而不是 12 次。所以代码里用了num num * 10 int(ch)做累加这是处理多位数数字的标准写法。另一个坑是]出栈后的拼接顺序。pop()出来的是[之前已经拼好的字符串也就是示例里的外层前缀所以必须写成prev cur * repeat不能反过来。我在第一次写的时候就栽在这了输出变成了bcbcbcaaaaaa这种奇怪的结果。这类题目没什么高深算法但细节特别多平时写的时候就要养成边写边在脑子里模拟一个简单用例的习惯。2.2 第二题·最长无重复子串滑动窗口的边界功夫2.2.1 题目描述与双指针思路题目很经典给定一个字符串比如abcabcbb找出其中不含有重复字符的最长子串的长度答案是 3对应子串abc。这题在 LeetCode 上是第 3 题机试也经常以各种变体出现。解法的核心思想是滑动窗口用两个指针维护当前无重复区间。右指针负责扩展遇到重复字符时左指针跳到上一个相同字符的后面。很多人第一反应是用集合存当前窗口的字符但那样需要配合循环去重更优雅也更不容易出 bug 的做法是直接用哈希表记录每个字符最后出现的位置。2.2.2 代码实现与易错点def max_unique_len(s: str) - int: last_pos {} # 字符 - 最后一次出现的下标 left 0 ans 0 for right, ch in enumerate(s): # 如果 ch 出现过且出现位置在窗口内左边界移动 if ch in last_pos and last_pos[ch] left: left last_pos[ch] 1 last_pos[ch] right ans max(ans, right - left 1) return ans s input().strip() print(max_unique_len(s))最关键的判断是last_pos[ch] left这个条件不能省。为啥因为last_pos记录的是字符在整个字符串中最后一次出现的位置但如果这个位置已经在左边界左边了说明它不在当前窗口内没必要为了它收缩窗口。少了这个判断窗口可能不能正确收缩答案也会出错。我举一个实际例子字符串abba。如果不用 left这个条件遍历到最后一个a时last_pos[a]是 0而当前left已经是 2如果直接left last_pos[a] 1left 会回退到 1窗口长度反而变长了。这个回退错误是这题最容易犯的几乎每次讲解这题我都会单独拎出来强调。2.3 第三题·最长递增子序列动态规划的两种层次2.3.1 题目描述与 DP 基础解法题目给定一个整数数组比如[10, 9, 2, 5, 3, 7, 101, 18]要求返回最长严格递增子序列的长度。这里子序列不要求连续但元素的相对顺序不能变。答案是 4对应[2, 3, 7, 101]或[2, 5, 7, 101]。基础解法是动态规划定义dp[i]表示以第i个元素结尾的最长递增子序列长度。状态转移需要遍历所有j i如果nums[j] nums[i]就用dp[j] 1更新dp[i]。初始值都是 1因为每个元素自身就是一个长度为 1 的子序列。def lis(nums): if not nums: return 0 n len(nums) dp [1] * n ans 1 for i in range(n): for j in range(i): if nums[j] nums[i]: dp[i] max(dp[i], dp[j] 1) ans max(ans, dp[i]) return ans nums list(map(int, input().split())) print(lis(nums))这个解法 O(n^2)数据范围 10^3 以内随便跑但如果 n 到了 10^5 就会被卡超时。机试题目一般会把数据范围写在题目描述里做题前先扫一眼数据范围这比什么都重要。2.3.2 贪心 二分的进阶写法如果要处理 10^5 级别的数据就得用贪心加二分的思路。维护一个tails数组其中tails[k]表示长度为k1的递增子序列中末尾元素最小的那个值。遍历每个数在tails里找第一个大于等于当前数的位置替换掉如果当前数比tails里所有元素都大就追加到末尾。import bisect def lis_greedy(nums): tails [] for x in nums: pos bisect.bisect_left(tails, x) if pos len(tails): tails.append(x) else: tails[pos] x return len(tails)我最初学这个方法的时候一直有个困惑tails数组替换来替换去最后得到的数组并不是真正的递增子序列为什么长度就对了后来想明白了tails本质上维护的不是一个真实存在的序列而是每个长度下的最小末尾值这个信息。长度相同的子序列末尾越小越有可能在后面接上更大的数。这也是贪心思想的体现。我当时做模拟题7的第三题第一时间写的是 O(n^2) 的 DP因为怕二分写错反而丢分。测了一下数据范围不大就保留了。机试的原则是够用就好先保证正确性再考虑优化没必要为了炫技去写复杂解法。2.4 第四题·任务调度模拟流程题最考验全局观2.4.1 题目描述与输入输出格式这题是这样的给定 N 个任务每个任务包含到达时间、执行时长和优先级数字越小优先级越高。CPU 每个整数时间点从已到达且未完成的任务里选优先级最高的执行 1 个单位时间优先级相同则先到达的先执行。要求输出每个任务的完成时间。输入格式3 0 5 2 1 2 1 2 4 3第一行是任务数 N后面 N 行每行是任务的到达时间、执行时长、优先级。任务按输入顺序编号为 0、1、2。输出格式按编号输出每个任务的完成时间用空格分隔。2.4.2 优先队列模拟思路模拟题的核心是找对主循环。这里最直观的主循环是时间步进从 0 开始每一秒做三件事先把所有到达时间小于等于当前时间且未入堆的任务放入优先队列然后从堆里弹出一个任务执行 1 个时间单位如果任务没执行完就重新入堆执行完了就记录完成时间。优先队列最小堆存什么这里有个细节要让堆顶始终是优先级最高的任务。Python 的heapq默认是最小堆所以直接存(优先级, 到达时间, 任务编号, 剩余时长)这样优先级数字小的先弹出优先级相同就到达时间小的先弹出。import heapq def schedule(n, tasks): tasks.sort() # 按到达时间排序 heap [] idx 0 time 0 ans [0] * n while idx n or heap: # 堆为空但还有任务没到直接把时间跳到下一个任务到达时刻 if not heap and idx n and time tasks[idx][0]: time tasks[idx][0] while idx n and tasks[idx][0] time: arrive, duration, priority, tid tasks[idx] heapq.heappush(heap, (priority, arrive, tid, duration)) idx 1 if heap: priority, arrive, tid, remain heapq.heappop(heap) if remain - 1 0: heapq.heappush(heap, (priority, arrive, tid, remain - 1)) else: ans[tid] time 1 # 当前时间点执行完后完成 time 1 return ans n int(input()) tasks [] for i in range(n): a, d, p map(int, input().split()) tasks.append((a, d, p, i)) ans schedule(n, tasks) print( .join(map(str, ans)))2.4.3 这题容易错在哪第一个坑是堆里比较元的顺序。如果不把到达时间放进去优先级相同但先到达的任务可能排到后面破坏题目条件的先到先执行。这种错误在本地测简单的两三个任务时可能看不出来但只要用例里出现同优先级任务就会挂掉。第二个坑是时间跳跃。如果堆空了但下一个任务到达时间还是 100还傻乎乎一秒一秒加到 100虽然结果对但效率很低。代码里用了一个if not heap and idx n and time tasks[idx][0]的情况直接把 time 跳到下一个到达时间这是模拟题常用的优化技巧。第三个坑是完成时间的计算。我用time 1是因为任务在 当前秒 执行完比如 time0 时执行一个时长 1 的任务它在第 1 秒结束所以完成时间是 1。很多人会写成time或者time 2一测边界就露馅。写模拟题之前先在草稿纸上把第一组简单的数据手动推一遍确认你对时间点的定义这个习惯能帮你省下不少调试时间。3. 从读题到 AC 的一整套实战流程3.1 拿到题目先别急着敲代码我自己踩过最大的坑就是读题太急。前几次模拟考题目看个大概就开始写写到一半发现输入格式理解错了推倒重来时间全浪费了。后来总结了一套流程现在每次做机试题都这么执行。先读三遍。第一遍泛读理解题目在说什么输入是什么输出是什么。第二遍精读把输入输出的边界条件划出来比如整数范围字符串长度是否可能为空。第三遍是拿题目给的样例手动推一遍确认你对规则的理解和样例输出能对上。这个过程看着慢实际上能防止后期返工是最省时间的做法。然后是写注释式伪代码。不要直接写实现先在代码注释里写出整体框架读入数据 - 核心处理 - 输出结果。等框架清楚了再往里面填逻辑。像任务调度那题我就是先写了# 1. 读入所有任务 # 2. 按到达时间排序 # 3. 时间从0开始每步把到达任务入堆 - 执行一个任务 - 记录完成时间 # 4. 输出结果框架定了后面的实现就是按部就班。头脑清醒的状态下写代码出错率能低一半。3.2 调试、边界测试和性能优化代码写完先别急着提交花两分钟做三件事。第一用题目给的样例跑一遍确认输出一致。第二自己构造几个边界用例空输入、最小输入、全是重复值、数值极大。第三检查输出格式结尾有没有多余空格、有没有缺失换行。这些细节虽然不涉及算法但扣的分跟算法错误一样多。调试时最常用的还是打印中间变量。比如字符串解压缩那题如果输出不对就在每个]处理的地方打印一下prev、repeat和cur基本一眼就能看出是拼接顺序问题还是数字累加问题。机试环境一般不支持断点调试所以在代码里临时加print是最直接的排查手段排完记得删掉。关于性能优化有一个原则先暴力再优化。第一版不管是 O(n^2) 还是 O(n^3)只要能跑出正确结果就先把暴力写出来保底。然后再根据数据范围决定要不要优化。如果 n 是 100O(n^3) 也没问题如果 n 是 10^5O(n^2) 就可能超时。机试的时间限制通常比较紧所以我在写完暴力版本后一定会看一眼数据范围评估是否需要用更优解法替换。4. 不同基础怎么安排刷题路线4.1 按基础水平的三条备考路线机试备考最忌讳的就是一刀切式刷题。我见过基础很弱的人上来就死磕困难动态规划也见过刷了几百题的人还在反复做简单字符串题这两种都是时间浪费。我把备考的人粗分为三类对应不同的侧重点。基础较弱的先别着急刷题花两三天把语言基础补到位重点练字符串处理、列表/数组操作、常见数据结构的增删查改。然后每天做两到三道简单题刷题时要求自己不看题解独立完成。这个阶段的目标不是刷多少题而是建立读题 - 写代码 - 调试通过的闭环信心。考试时目标要明确保住前两题第三题写暴力拿部分分。有一定基础、刷过 100 到 200 题的重点是按专题突破。一周里安排字符串、双指针、动态规划、模拟四个专题每个专题集中刷两天。这个阶段最容易陷入的误区是舒适区刷题简单题刷得飞起一碰到动态规划就跳过。一定要逼自己每天至少做一道不会的题哪怕想不出来看了解析后要能独立复现。目标就是前三题稳定 AC第四题争取相当部分用例。基础扎实、刷题 300 以上的重心要放在机试实战化上。这个时候纯题目已经不太能提高能力了关键是把平时刷题的方式切换到限时模式每周至少两次完整模拟严格按照机试的时长和单题时间分配来练习。重点训练的是在时间压力下怎么选择策略、怎么快速排查边界条件、怎么写代码一遍过。目标就是全卷稳定高分。4.2 刷题优先级与每日时间分配根据考点频率我建议按这样的优先级排序备考字符串处理、双指针/滑动窗口、排序/贪心、动态规划、流程模拟、DFS/BFS。前四类是必争之地后面的看时间分配。机试的高频题大多集中在这些方向把它们练熟效果比盲目刷 500 道冷门题好得多。时间分配上如果只剩一周重心放在简单题的熟练度和字符串处理上每天一套限时模拟模拟完立刻复盘把卡壳的题整理到错题本。如果有两周到一个月第一周按专题刷中档题第二周开始每周两到三次限时模拟。如果有一个月以上可以系统做专题刷题每天保持一小时量周末完整模拟一套。错题本这个东西我建议只记原因不抄题目。比如记录这题卡住是因为没考虑负数输入模拟题超时是因为循环里重复扫描数组这类结论而不是把整道题抄一遍。考前过一遍错题本比临时翻阅题单有用得多。5. 机试避坑指南这些细节决定成败5.1 高频踩坑点速查表我把机试里常见的问题整理成一张表这些都是实际操作中反复出现的坑问题类型具体表现解决办法输入读取题目是多组输入只读了一组先用样例测一下观察是否有 EOF 标志输入读取输入数字跨多行input().split()只拿到一部分用sys.stdin.read().split()统一读入再解析输出格式行尾多了一个空格最后一个元素单独打印或者用 .join()拼接输出格式大小写不匹配按题目要求原样输出别自己修正递归Python 递归深度不够能用迭代就别递归必要时sys.setrecursionlimit(1000000)性能O(n^2) 在大数据下超时先看数据范围提前想好优化方案性能Python 字符串频繁拼接很慢结果用列表收集最后.join()数组越界访问下标为 -1 的元素没意识到涉及索引运算时用边界值手动推一遍初始化数组默认值没设置对特别注意dp数组初始值是否应该是 1 而不是 0第 5 行那个多行输入的问题我单独解释一下。机试的输入有时不给明确的行数数据可能换行也可能不换行如果用input().split()就会漏读。更稳妥的写法是import sys data list(map(int, sys.stdin.read().split()))这样不管数据切成几行都能一次性全部读进来。我后来做模拟题7的时候只要是读一堆整数但不确定行数的题默认就用这种写法省心很多。5.2 考场战术与心态机试的节奏和平时练习完全不同。我的经验是拿到题目后先全部扫一遍10 秒内判断每道题的难度然后从最简单的开始做。先保证把简单题的满分拿到手再去啃难题不要在第一题上卡 40 分钟导致后面两道题连读题时间都没有。还有一个很现实的策略对每道题设置一个时间上限。比如简单题最多 20 分钟中档题最多 30 分钟超过上限就立刻切换到保底模式——把暴力解法写上用例能过多少算多少。这样做的原因是机试按用例比例给分与其在一个难题上耗到最后一无所有不如保证已经拿到的分不丢。心态方面机试的限时环境很容易放大人的焦虑感尤其是前面一道题不顺的时候。我自己的调节方法是卡住 10 分钟就想这题最多 30 分后面还有 70 分该跳就跳别让一道题毁掉整场状态。做过几次限时模拟之后你会发现紧张感会明显降低熟练度是缓解焦虑的最好方式。最后再分享一个小技巧做模拟题7的过程中我收获最大的一件事是养成了每题写完都手动跑一遍边界用例的习惯。以前总觉得自己思路对就完事了结果每次出问题的都是那些看似正常的边界条件比如空字符串、长度为 1 的数组、全是相同字符的串。现在写完后我会盯着代码想如果输入是极端情况我的代码会走哪条分支有没有可能越界这个习惯在机试里救了我好几次。如果你现在刚开始准备机试我的建议很直接找一套模拟题限定时间完整做一遍然后根据暴露出来的问题调整复习方向。做过一套之后你对自己的短板就心里有数了比自己闷头刷几十道题都管用。
返回列表