ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

华为机试模拟题7解析:设备告警统计与滑动窗口实战

2026/8/29 8:29:39 拓冰建站 浏览量
华为机试模拟题7解析:设备告警统计与滑动窗口实战 1. 华为机试到底考什么为什么必须刷模拟题先说点实际的。华为机试包括OD机试对于很多求职者来说是第一道硬门槛尤其是校招和OD岗位笔试过了才有后续面试的机会。早些年可能随便刷刷题就能应付但现在的机考已经非常规范新系统普遍是双机位监控C卷题库滚动更新题型固定但覆盖面广完全是“不刷题基本过不了刷对题才有可能稳过”的状态。我自己带过几个准备华为OD机试的朋友也帮人在牛客和CSDN上整理过真题题库目录一个非常直观的感受是模拟题的价值不在于猜中原题而在于帮你建立做题的节奏和套路。真实机考约两个半小时、三道题一道100分、两道200分总分400通常150分左右过线但难度不是线性分布的。第一题属于送分题考字符串处理、简单模拟第二题开始上强度涉及哈希表、双指针、贪心、BFS/DFS第三题往往是综合题动态规划或者复杂模拟非常考验代码功底。所以你在刷“华为机试编程模拟题7”这类套题时首先需要建立的是对题目结构的敏感度。拿到题先判断题型再决定暴力解还是优化解最后留足时间调试边界条件。这篇文章我就以一道典型的模拟题为例完整拆解从审题到AC的全部过程并把机考环境下的细节一并讲清楚。2. 模拟题7整体设计思路与考点分布2.1 这道题的题目背景与业务场景华为机试有一个很典型的特点题目往往会包装成一个业务场景比如设备告警、工单调度、日志分析、端口配置让你假装在解决一个真实的运维或开发问题。模拟题7就是这样一个场景题核心背景是设备告警流水统计。题目大意是系统持续收到多条设备告警记录每条记录包含设备编号字符串、告警时间格式为HH:mm24小时制和告警级别1到4的数字数字越大越严重。现在需要统计出每个设备在任意连续60分钟内告警次数达到或超过阈值K的所有时间段并按规则输出。这个背景其实非常贴近华为的设备运维业务。你在网上看到的“华为smartkit”“华为交换机运维”“华为防火墙HRP实验”这些词本质上都是围绕设备管理的。机试把这类场景抽象成一道算法题既考编程能力也考你理解业务规则的能力。2.2 涉及的核心算法知识点拆解这道题表面上是字符串解析和时间窗口问题但真正考察的知识点有四个层次字符串解析输入是若干行每行用逗号或空格分隔设备编号可能包含字母、数字、横杠比如Device-01时间格式固定为HH:mm需要转换成分钟整数才能比较大小。排序处理同一设备的告警记录需要按时间升序排列这是后续滑动窗口的前提。滑动窗口/双指针判断连续60分钟内告警次数是否超过阈值经典固定窗口问题。窗口内告警数达到K时记录窗口的起始时间和结束时间。区间合并同一个设备可能输出多个重叠或相邻的时间段需要按规则合并比如起始时间相同取结束时间更晚的或者相邻区间合并这一步最容易出错。此外还涉及哈希表Map对每个设备的告警列表分组。说句实话这道题就是“哈希表 排序 滑动窗口 区间合并”的组合拳完全是OD机试第二题的难度水平。如果第三题出这个通常会把数据范围加大到必须用差分数组或线段树但模拟题阶段先掌握基础实现就够了。2.3 为什么选择用“时间窗口”而不是“逐分钟扫描”做题的时候很多人第一反应是把一天的时间按分钟拆成1440个格子然后每一分钟判断一次当前设备在接下来60分钟内的告警数。这种方法在数据量小的时候没问题但一旦设备多、告警记录多复杂度就会变成O(记录数 * 60)相当于每条告警都要尝试作为窗口起点然后用内层循环数窗口内的数量。我算过一笔账假设有1000台设备每台设备1000条告警总记录数是100万条如果用逐分钟扫描的方式每条记录作为窗口起点后内层循环要遍历窗口内的记录最坏情况是O(100万 * 1000)也就是10亿次操作在机考环境里铁定超时。滑动窗口的核心思路是把内层循环的重复计数消掉。左右指针都只前进不后退右指针负责扩展窗口左指针负责收缩窗口这样每个记录最多被访问两次时间复杂度直接降到O(N log N)排序的部分。这也是为什么我说刷模拟题要重点练“双指针”和“滑动窗口”这类模板化算法因为它们在机试里的出镜率实在是太高了。3. 模拟题7核心解析输入输出、样例与边界条件3.1 输入输出格式详解华为机试的输入输出格式非常固定所有题目都用标准输入输出不搞什么文件读写。这道题也不例外。输入 第一行两个整数 N 和 KN 表示告警记录总数K 表示阈值。 接下来 N 行每行格式为 设备编号 告警时间 告警级别 设备编号是长度不超过20的字符串可能包含空格不会机试通常避免这种坑。 告警时间格式为 HH:mm保证 00:00 到 23:59 之间。 告警级别是 1 到 4 的整数。 输出 按照设备编号字典序升序输出每个设备满足条件的告警时间段。 每个设备先输出一行设备编号然后输出若干行时间段格式为 起始时间-结束时间 其中起始时间是该连续60分钟窗口的开始分钟即窗口内第一条告警的时间结束时间是窗口内最后一条告警的时间都按 HH:mm 格式输出。 如果一个设备没有满足条件的时间段该设备不输出。这里有几个隐含的细节必须留意阈值K的含义是“大于等于K”还是“大于K”题目一般会说清楚但模拟题里很多时候默认是“达到或超过K”。如果题目用的是“超过”那就是如果是“达到”就是。我刷题时习惯先把题目的原文划线标注特别是这种临界词错了直接0分。窗口的边界连续60分钟比如10:30到11:29是60分钟吗严格说是59分钟不对10:30到11:30是60分钟。这里涉及到时间段计算机试通常认为从10:30开始窗口覆盖到11:29也就是start 记录时间 start 60。为了统一我建议把时间全部转成“分钟数”HH * 60 MM然后窗口条件就是recordTime - startTime 60。时间段的输出当窗口内告警次数达到K后输出的起始时间取窗口内第一条告警的时间结束时间取窗口内最后一条告警的时间。很多人会误以为输出的是窗口的边界比如起始时间是startTime而不是第一条告警的时间。这里必须看题目要求如果是“窗口内第一条告警时间到窗口内最后一条告警时间”那就是按告警时间输出。3.2 样例数据与手工推演为了说清楚我构造一个具体的样例输入 8 3 Device-01 10:00 2 Device-01 10:10 3 Device-01 10:20 1 Device-01 11:00 4 Device-02 09:00 2 Device-02 09:30 3 Device-02 10:00 1 Device-02 10:30 2设备Device-01有4条告警时间分别是10:00、10:10、10:20、11:00。阈值K3。按时间排序后用滑动窗口看窗口起点10:00窗口内时间是[10:00, 11:00)所以10:00、10:10、10:20都在窗口内数量为3满足阈值。窗口输出10:00-10:20。窗口起点10:10窗口内时间是[10:10, 11:10)10:10、10:20、11:00都在窗口内数量为3也满足。窗口输出10:10-11:00。窗口起点10:20窗口内是[10:20, 11:20)10:20、11:00两条数量2不满足。两个时间段分别是10:00-10:20和10:10-11:00。这两个区间有重叠需要按规则合并。题目如果要求合并那么结果就是10:00-11:00如果不要求合并则输出两行。我刷过的模拟题中很多时候要求合并相邻或重叠区间这是一个高频考点。设备Device-02有4条告警9:00、9:30、10:00、10:30任意连续60分钟内最多几条呢窗口起点9:00窗口内[9:00, 10:00)包含9:00、9:30数量2不满足。窗口起点9:30窗口内[9:30, 10:30)包含9:30、10:00数量2不满足。窗口起点10:00窗口内[10:00, 11:00)包含10:00、10:30数量2不满足。所以Device-02不满足输出条件。最终输出Device-01 10:00-11:00等等这里要注意如果按合并区间的方式10:00-10:20和10:10-11:00合并为10:00-11:00。但如果题目不要求合并而是要求“输出所有满足条件的窗口”结果就是两行。这个必须根据题目描述来我在下面会讲到如何灵活处理。3.3 隐藏的边界条件与机试陷阱说实话这种题目真正难的不是算法而是边界条件。我总结了四个机试里最爱埋的坑设备编号可能相同但大小写不同device-01和Device-01如果题目没说忽略大小写那就按不同的设备处理。哈希表的key直接用原始字符串别自己加toLowerCase因为机试判题是按照题目规则的。时间跨零点所有告警都是在同一天内吗机试通常限定在一天内所以不存在跨天的情况。但如果是当天23:59的告警往后延长60分钟会超过00:00这种情况一般直接忽略因为一天内没有更多告警了。输入乱序N行记录不是按设备分组也不是按时间排序的而是随机乱序。所以第一步必须做“按设备分组 组内按时间排序”这一步漏了后面全错。输出格式的细节时间输出必须补零比如09:05而不是9:5。用Java的String.format或者Python的zfill别手写判断。这些坑看起来小但机试是按用例给分的一个用例没过就扣对应分数。很多人的代码逻辑完全正确就因为输出格式少了前导零白白扣了20分甚至更多。4. 实操过程从零到AC的完整代码实现4.1 整体数据结构设计我用Java来写因为华为OD机试主流的可选语言是Java、C和Python但Java在华为内部使用最广泛。用Python写自然更短但有些考生对Java更熟悉这篇我以Java为例同时附上Python版本的思路差异。第一步定义告警记录类static class Record { String deviceId; int time; // 转换成分钟数 int level; Record(String deviceId, int time, int level) { this.deviceId deviceId; this.time time; this.level level; } }为什么要转成分钟数因为有字符串比较会有问题10:00和9:00按字典序比较10:00反而小于9:00因为字符1小于9。虽然时间格式固定HH:mm前导零补齐后字典序就是时间序但转成整数更保险后续滑动窗口也更好写。时间转换方法static int toMinutes(String hhmm) { String[] parts hhmm.split(:); return Integer.parseInt(parts[0]) * 60 Integer.parseInt(parts[1]); }反向方法static String toHHMM(int minutes) { return String.format(%02d:%02d, minutes / 60, minutes % 60); }这里有一个小技巧String.format(%02d:%02d, ...)自动补齐前导零比自己拼字符串安全得多。4.2 分设备排序与滑动窗口核心逻辑第二步读取输入按设备分组MapString, ListRecord map new HashMap(); for (int i 0; i N; i) { String deviceId in.next(); String timeStr in.next(); int level in.nextInt(); Record record new Record(deviceId, toMinutes(timeStr), level); map.computeIfAbsent(deviceId, k - new ArrayList()).add(record); }第三步对每个设备的记录列表按时间排序for (String deviceId : map.keySet()) { ListRecord records map.get(deviceId); records.sort(Comparator.comparingInt(r - r.time)); }这里注意Comparator.comparingInt(r - r.time)是Java 8的写法如果机试环境只支持Java 7就得改成Collections.sort(records, new ComparatorRecord() { ... })。第四步滑动窗口统计。代码如下for (String deviceId : map.keySet()) { ListRecord records map.get(deviceId); int left 0; Listint[] intervals new ArrayList(); for (int right 0; right records.size(); right) { // 保持窗口内所有记录时间差小于60分钟 while (records.get(right).time - records.get(left).time 60) { left; } int count right - left 1; if (count K) { // 窗口内记录数量达到阈值记录窗口内第一条和最后一条告警的时间 int start records.get(left).time; int end records.get(right).time; intervals.add(new int[]{start, end}); } } // 合并区间并输出 }这段代码的核心逻辑我拆开讲一下right指针不停向右扩展把新的告警记录纳入窗口。while循环负责从左侧收缩窗口保证窗口中所有记录的时间跨度小于60分钟。为什么是 60就左移因为窗口左边界和当前记录的时间差如果超过或等于60分钟说明当前记录已经不在“以left为起点的60分钟窗口”内了必须移动left。收缩完之后right - left 1就是当前窗口内的告警总数如果大于等于K就找到了一个满足条件的窗口。这里记录的是records.get(left).time和records.get(right).time也就是窗口内最早和最晚的告警时间而不是窗口边界本身。4.3 区间合并与最终输出为什么需要区间合并因为同一个设备滑动窗口可能存在大量重叠区间。比如一个设备从10:00到11:00密集告警滑动窗口在10:00、10:01、10:02各个起点都会产生一个新的区间如果不合并输出结果会有几百行而且很多区间起始时间相同、结束时间不同判题系统也没有办法精确匹配。区间合并的思路是先把所有区间按起始时间排序然后依次判断当前区间是否和前一个区间重叠或相邻。重叠的定义是current.start previous.end注意这里要用还是取决于题目对“相邻区间”的定义。我建议用因为10:00-10:20和10:20-11:00这两个区间严格来说是首尾相接的合并后应该是一个完整的时间段10:00-11:00。具体代码如下intervals.sort((a, b) - a[0] ! b[0] ? a[0] - b[0] : a[1] - b[1]); Listint[] merged new ArrayList(); for (int[] interval : intervals) { if (merged.isEmpty() || interval[0] merged.get(merged.size() - 1)[1]) { merged.add(interval); } else { merged.get(merged.size() - 1)[1] Math.max( merged.get(merged.size() - 1)[1], interval[1] ); } }合并完按设备编号字典序排序后输出ListString deviceIds new ArrayList(map.keySet()); Collections.sort(deviceIds); for (String deviceId : deviceIds) { Listint[] merged mergeIntervals(intervals); if (merged.isEmpty()) continue; System.out.println(deviceId); for (int[] interval : merged) { System.out.println(toHHMM(interval[0]) - toHHMM(interval[1])); } }4.4 Python版本参考与思路对比如果机试选Python思路完全一样但代码简洁得多。我用Python实现一遍核心逻辑大家可以对比一下from collections import defaultdict def to_minutes(s): h, m map(int, s.split(:)) return h * 60 m def to_hhmm(t): return f{t // 60:02d}:{t % 60:02d} N, K map(int, input().split()) device_map defaultdict(list) for _ in range(N): device, time_str, level input().split() device_map[device].append((to_minutes(time_str), int(level))) for device in sorted(device_map.keys()): records sorted(device_map[device]) left 0 intervals [] for right in range(len(records)): while records[right][0] - records[left][0] 60: left 1 if right - left 1 K: intervals.append((records[left][0], records[right][0])) # 合并区间 intervals.sort() merged [] for start, end in intervals: if not merged or start merged[-1][1]: merged.append((start, end)) else: merged[-1] (merged[-1][0], max(merged[-1][1], end)) if merged: print(device) for start, end in merged: print(f{to_hhmm(start)}-{to_hhmm(end)})Python的defaultdict(list)比Java的computeIfAbsent简洁不少刷题时用Python写这类题确实快很多。但如果你的目标岗位明确要求Java还是建议用Java刷别到了机试现场才切换语言。5. 机试现场实战经验这样刷模拟题才有效5.1 双机位环境下的做题节奏管理现在的华为OD机试采用双机位监控一个摄像头对着你另一个对着你的屏幕和手部。整个过程中系统会随机抓拍而且浏览器不能切出考试页面切出会记录异常行为。这意味着你不能像平时刷LeetCode那样中途百度搜一下API用法。所以平时刷模拟题的时候建议养成三个习惯重要API要背比如Java的String.format、String.split、HashMap.computeIfAbsent、Collections.sortPython的defaultdict、sorted(key...)、f-string。这些高频API如果每个都去查考试时心态直接崩。写完代码先手测样例机试环境不会有IDE的智能提示也不一定有断点调试工具有些平台提供在线IDE但调试能力很弱。所以必须学会在提交前用题目给的样例数据手跑一遍代码模拟执行过程。预留至少30分钟检查三道题两个半小时我的习惯是第一题40分钟、第二题60分钟、第三题60分钟剩下30分钟专门检查边界。如果第二题卡住了先跳过做第三题别死磕。5.2 如何用模拟题建立“题库思维”我非常推荐把历年真题和模拟题按照考点做分类整理。比如我自己整理过一个表格把华为OD机试常见的题目按考点分成五类考点分类典型题型备考优先级字符串处理字符串反转、括号匹配、IP地址校验高必考模拟与哈希表告警统计、工单分配、LRU高必考排序与贪心会议安排、任务调度中高动态规划背包问题、最长子序列中第三题常考图论与搜索BFS迷宫、DFS岛屿中低偶尔考有了这个分类刷模拟题7这种题目时你就知道它其实属于“模拟与哈希表”大类和“LRU缓存”“工单调度”是同一族。刷透一题等于刷透一类这个效率远比无脑刷几百道题要高。5.3 模拟题中经常出现的“变体”机试题库是滚动更新的同一个场景可以衍生出不同的考法。我在刷模拟题过程中发现“告警统计”这类场景经常出现以下变体变体一输出阈值首次达到的时间。不要求输出完整区间只要求找到每个设备第一次达到阈值的时间点这种题直接滑动窗口找到一个就break。变体二告警级别加权。每条告警不再是简单的计数1而是按级别加权比如级别4记3分、级别3记2分窗口内累计积分达到K才算。这样滑动窗口的计数逻辑要改成累加权重但窗口收缩逻辑不变。变体三时间窗口不固定。比如要求窗口覆盖的“首次告警和最后告警之间的时间跨度不超过60分钟”而且要求窗口内至少K条但窗口起点不一定是第一条告警的时间可能是任意分钟。这种题更复杂需要枚举窗口起点或使用前缀和数组。变体四多个设备合并输出。把不同设备在同一时间段内的告警合并统计比如“全系统任意60分钟内告警总数超过K”那就把所有设备的记录合并到一条时间轴上排序再跑一次滑动窗口。每一次变体其实是在原题基础上加一个条件或改一个细节。所以刷题时别只满足于AC一个版本可以自己尝试改条件重新实现一遍这个过程对理解算法本质非常有帮助。6. 常见问题与排查技巧实录6.1 华为机试高频报错与解决思路我总结了机试过程中最容易踩的六个坑每个都有对应的排查思路问题一输出格式错误现象本地跑样例全对提交后0分。排查检查时间是否补零、设备编号顺序是否字典序、-分隔符是否正确、每一行是否有多余空格。机试判题对格式极其严格多一个空格都是错。问题二超时现象大数据量用例超时。排查看代码里是否有三重循环尤其是while嵌套for。滑动窗口的右指针虽然有个内部的while但每个元素最多被left和right各访问一次复杂度是O(N)。如果你写的是暴力解法数据量超过10万基本就挂了。问题三输出区间重叠未合并现象样例能过但结果和预期不符。排查打印出未合并的区间看是否有重叠或相邻的情况。我遇到过一个题目输出要求“时间区间不能重叠”如果直接输出所有滑动窗口结果会有一堆重叠区间必须合并。问题四设备编号排序不正确现象输出设备和预期顺序不一样。排查华为机试的字典序排序使用ASCII码不是中文拼音。Device-02会排在Device-10后面因为0的ASCII码小于1。如果你手动实现了排序注意用compareTo而不是自然语言排序。问题五内存溢出现象程序在最大数据量直接崩溃。排查检查是不是把所有记录都存成了字符串对象没有转成整数。1万条记录还好100万条记录如果用String存时间会占用大量内存建议直接用int。问题六输入读取不完整现象某些用例只读到了一部分数据。排查用BufferedReader.readLine()读取时注意可能有多余的空行用Scanner时要注意hasNextLine()和hasNext()的差异。机试输入通常没有空行但保险起见还是用Scanner的next方法逐项读取。6.2 一道题的完整自测用例设计方法很多考生在本地写代码时只用题目给的样例跑一遍然后就提交这非常危险。样例通常只覆盖正常情况不覆盖边界。我建议每道题至少设计以下五类自测用例用例类型示例目的最小输入N1, K1验证程序不会因数组越界崩溃全不满足K100, 只有3条告警验证输出为空时不会出错全满足所有告警都在同一分钟内验证滑动窗口在极端情况下的行为边界时间00:00 和 23:59验证时间转换和补零临界值窗口内正好K条、正好K1条验证和的区别比如这道题我建议一定要测一下“多条告警时间相同”的情况。假设10:00有两条告警10:01有一条K3那么10:00作为窗口起点窗口内三条告警都满足输出应该是10:00-10:01。如果滑动窗口没有处理相同时间的记录很可能漏掉。6.3 手把手调试一个逻辑错误说一个真实案例。我有一次给朋友改这道题的代码发现他写的滑动窗口区间判断是这样的while (records.get(right).time - records.get(left).time 60) { left; }这个写法在时间差 60时不会收缩窗口。比如告警时间是10:00和11:00时间差正好60分钟。按题目定义[10:00, 11:00)这个窗口内其实只包含10:00不包含11:00因为结束时间是开区间。但如果不收缩窗口代码会把11:00也纳入窗口内导致计数多算了一条。这类“开区间还是闭区间”的问题是滑动窗口题里最高频的bug。解决思路其实很简单把时间差换算成“分钟数之差小于60”坚持用 60作为窗口内条件不要用 60。如果题目定义的窗口是闭区间包含两端那才能用 60但60分钟窗口在分钟级粒度下10:00到11:00算61个分钟点包括两端不符合“连续60分钟”的直觉。我在刷题时给自己定了一条规则凡是时间窗口类题目先确定是开区间还是闭区间再写while判断条件。如果是[start, start60)用 60左移如果是[start, start60]用 60左移。这道题因为告警是离散的时间点所以用开区间来判断更符合离散事件的语义。7. 最后分享一个提升速度的做题小习惯在做完这道模拟题之后还有一个非常实际的问题如何在有限的机试时间内快速写出正确的代码。我发现很多考生不是不会做而是做的时候犹豫先写排序还是先写滑动窗口中间变量用什么结构存要不要单独写一个合并函数根据我自己的经验解决这个问题的方法是建立“代码模板”。比如滑动窗口类题目的模板我在本地保存了一份定期复习int left 0; for (int right 0; right arr.length; right) { // 1. 向窗口加入 arr[right] // 2. while 窗口不满足条件时移动 left 收缩窗口 // 3. 判断窗口是否满足条件并更新结果 }这道模拟题本质就是在这个模板上加了“时间转换”“分组排序”“区间合并”三个外壳。模板记住之后考试时只需要花时间处理外壳的业务逻辑不必从头推导核心算法速度会快很多。最后再提醒一句华为机试的模拟题和真题是很接近的但模拟题毕竟是模拟题别指望考试时遇到一模一样的原题。把每一道模拟题吃透总结题型套路才是真正的备考策略。希望这篇关于模拟题7的拆解能对正在准备华为机试的朋友有实际帮助。