news 2026/10/22 14:12:03

华为OD机考C卷:Java实现手机App防沉迷系统,1440数组秒解区间覆盖

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
华为OD机考C卷:Java实现手机App防沉迷系统,1440数组秒解区间覆盖

如果你最近在准备华为OD机考C卷,刷题时遇到“手机App防沉迷系统”这道题,又正好用Java写,十有八九会在区间合并上卡一下。我第一次看到它时,脑子里本能地开始设计区间树、合并列表,写了半天发现边界越来越乱。后来换了个思路:一天只有1440分钟,我为什么不让每一分钟都去找它归属的App?这个答案看起来“暴力”,在机考环境下却是最快的解法。

先说清楚,我下面整理的题面是根据网上流传的华为OD机考C卷版本做的还原,不是官方原题,但核心考点一致:给你若干App的优先级和使用时间段,计算每个App被高优先级App覆盖后实际使用时长。考场题目措辞可能微调,数据结构和解题思路可以直接复用。

1. C卷双机位下,我为什么把防沉迷题当成数组题做

1.1 双机位环境下,代码要“够简单”

华为OD机考是双机位监考,副机位对着键盘和屏幕。这种环境下最耗精力的不是题目本身,而是紧张导致的反复改代码。我见过不少人在前半小时纠结“这个区间要不要合并”,后半小时又在处理“合并完发现少了10分钟”。

所以我给自己定了一条机考原则:只要是数据规模可控的题,先用最直观的思路拿满分,再考虑要不要优化。“手机App防沉迷系统”就是典型。一天只有24小时,按分钟拆就是固定的1440个位置,完全没必要引入复杂的数据结构。

1.2 这类题到底在考什么:业务包装下的区间覆盖

题目看起来像一个业务系统,实际上考的是三点:

  • 时间区间解析:字符串转分钟数,左闭右开还是左闭右闭要搞清楚。
  • 优先级覆盖:同一分钟如果多个App都想占用,谁优先级高听谁的。
  • 结果统计:每个App被覆盖后最终拿到多少分钟。

说白了,这是一个“区间染色”问题。每个App的每个时间段就是一段染色区间,越往后颜色越深代表优先级越高,最后统计每种颜色染了多少分钟。数组解法就是把“染色”落到具体的分钟上。

1.3 为什么是1440分钟数组,而不是区间合并

区间合并的好处是时间上和区间数量相关,但坏处是覆盖关系复杂。比如A覆盖B的一部分,B又覆盖C的一部分,C又覆盖A的一部分,合并时很容易写出满屏if-else。

分钟数组的思路就简单太多:

  • 开一个长度1440的int数组。
  • 默认值是-1,表示这一分钟还没有归属。
  • 遍历所有App的所有时间段,每一分钟都判断“当前这个App能不能覆盖掉之前安排的App”。
  • 能就改成当前App的编号,不能就跳过。

这样覆盖关系天然成立,高优先级覆盖低优先级,同一分钟只会有一个归属。1440个元素的数组,内存占用不到6KB,时间上哪怕App再多,每个App时间段再碎,总操作数也就是“总分钟数”,最多1440乘以记录数,机考规模完全撑得住。

这是把区间题降维成数组题的过程。代码简短,逻辑直白,双机位盯着屏幕改bug时也不容易看花眼。

2. 题面与数据建模:防沉迷覆盖规则,本质是每分钟一个归属

2.1 我按这套输入输出约定来写

为了统一下文代码,我先把题面约定写清楚。如果你考场上遇到的版本描述不同,大概率也只是输入字段顺序或者时间分隔符有差异,核心模型不变。

输入格式:

第一行:一个整数N,表示配置记录的条数。
接下来N行,每行格式如下:

AppName priority hh:mm-hh:mm [hh:mm-hh:mm ...]

含义分别是:

  • AppName:应用名称,不带空格。
  • priority:优先级,取值范围一般是1到5,数字越大优先级越高。
  • 后面的时间段可以有一个或多个,格式是开始时间-结束时间。

输出格式:

按AppName第一次出现的顺序,每行输出AppName 实际使用分钟数。

样例输入:

2 Music 1 09:00-10:00 Video 5 09:30-10:30

样例输出:

Music 30 Video 60

解释:Music本应从09:00用到10:00,但09:30到10:00这段被更高优先级的Video覆盖,所以Music只剩30分钟。

先别急着看代码,建模型这一步想清楚,后面就是机械翻译。

2.2 优先级冲突的规则:高优先级覆盖低优先级

防沉迷系统的规则可以理解为:**任意一分钟,只属于当时正在运行的、优先级最高的那个App。**如果两个App在同一分钟重叠,低优先级的那一方在这一分钟的“使用记录”就不成立,时间算到高优先级App头上。

那如果优先级相同且时间段重叠呢?不同版本可能规则不同。我在下面的实现里采用了一个更容易让大多数人接受的规则:**同优先级,先出现的App优先。**也就是先输入的先保留。这一点我会在代码注释里标出来,如果你考场遇到“后设置覆盖先设置”,只需要把比较符号从<改成<=,逻辑不变。

2.3 时间区间的开闭:左闭右开

所有时间段默认是左闭右开,也就是09:00-10:00表示09:00整开始,10:00整结束,10:00那一分钟不属于这段。这个约定和数组下标天然匹配:遍历分钟时从start开始,到end减1结束。

有个细节:如果出现09:00-09:00,也就是start等于end,时长是0,直接忽略。防止有些同学把这种区间当成跨天处理。

2.4 跨天与同名App怎么处理

题目如果没有特别说明,一般都在同一天内。但为了稳妥,我在解析时间时对end < start的情况做了跨天拆分。比如22:00-01:00,把它拆成两段:

  • 22:00到24:00,也就是1320分钟到1440分钟。
  • 00:00到01:00,也就是0分钟到60分钟。

如果只统计“当天”的沉迷时长,那第一部分计入当天,第二部分就属于第二天的凌晨了。单日数组方案天然会把第二部分排除,结果就是只统计当天部分。这个处理在多数机考用例里够用。

同名App如果出现多次,我倾向于合并成一个App对象,优先级取所有记录里的最大值,时间段全部累加。这样一个App的总使用时长就是所有时间段的并集,而不是各自独立后互相覆盖,更符合“同一个App总时长”的业务直觉。

3. Java实现:分钟级扫描解法,可以直接抄的完整代码

3.1 数据结构设计

我定义了一个内部类App,包含名称、优先级、时间段列表。时间段用int[]数组表示,数组只有两个元素,start和end,单位是分钟。

为什么不用Java自带的时间类?因为机考在线编辑器上,用LocalTime或者Date反而容易踩格式化坑,而且字符串转分钟我们自己写两行就结束了,没必要引入依赖。

LinkedHashMap用来维护名称到App下标的映射,它可以保证遍历顺序是第一次插入的顺序,输出时天然满足“按第一次出现顺序输出”。

3.2 覆盖逻辑:owner数组怎么用

核心是下面这个数组:

int[] owner = new int[1440]; Arrays.fill(owner, -1);

owner[i]记录第i分钟最终归属于哪个App的下标,-1表示这一分钟没有被任何App使用。

接下来遍历所有App的所有时间段,对每一分钟执行覆盖判断:

if (owner[minute] == -1 || apps.get(owner[minute]).priority < app.priority) { owner[minute] = i; }

第一行是“这一分钟还没人用”,第二行是“当前App优先级更高”。如果当前App优先级低,就什么都不做,相当于被高优先级覆盖了。

同优先级先到先得的规则也体现在这里:只有当当前App优先级严格大于已有App时才会替换,等于时不替换。

最后统计时,从头到尾扫一遍owner数组,数一下每个App占了多少分钟,输出即可。

3.3 完整代码(Java)

import java.util.*; public class Main { static class App { String name; int priority; List<int[]> ranges = new ArrayList<>(); App(String name, int priority) { this.name = name; this.priority = priority; } } static int timeToMin(String time) { String[] parts = time.split(":"); return Integer.parseInt(parts[0]) * 60 + Integer.parseInt(parts[1]); } static void addRange(App app, int start, int end) { if (start < 0) { start = 0; } if (end > 1440) { end = 1440; } if (start >= end) { return; } app.ranges.add(new int[]{start, end}); } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = Integer.parseInt(sc.nextLine().trim()); List<App> apps = new ArrayList<>(); Map<String, Integer> nameIndex = new LinkedHashMap<>(); for (int i = 0; i < n; i++) { String line = sc.nextLine().trim(); if (line.isEmpty()) { i--; continue; } String[] parts = line.split("\\s+"); String name = parts[0]; int priority = Integer.parseInt(parts[1]); App app; if (nameIndex.containsKey(name)) { app = apps.get(nameIndex.get(name)); app.priority = Math.max(app.priority, priority); } else { app = new App(name, priority); nameIndex.put(name, apps.size()); apps.add(app); } for (int j = 2; j < parts.length; j++) { String[] times = parts[j].split("-"); int start = timeToMin(times[0]); int end = timeToMin(times[1]); // end < start,说明跨天,拆成两段处理 if (end < start) { addRange(app, start, 1440); addRange(app, 0, end); } else if (end > start) { addRange(app, start, end); } // end == start 视为0时长的无效区间,直接忽略 } } int[] owner = new int[1440]; Arrays.fill(owner, -1); for (int i = 0; i < apps.size(); i++) { App app = apps.get(i); for (int[] range : app.ranges) { for (int minute = range[0]; minute < range[1]; minute++) { // 同优先级保留先出现的App; // 如果题目规定“后设置覆盖”,把 < 改成 <= if (owner[minute] == -1 || apps.get(owner[minute]).priority < app.priority) { owner[minute] = i; } } } } int[] total = new int[apps.size()]; for (int minute = 0; minute < 1440; minute++) { if (owner[minute] != -1) { total[owner[minute]]++; } } StringBuilder sb = new StringBuilder(); for (int i = 0; i < apps.size(); i++) { sb.append(apps.get(i).name) .append(' ') .append(total[i]) .append('\n'); } System.out.print(sb.toString()); } }

3.4 自己造的三组用例验证

写完不要急着提交,先手动造几组用例。下面是我测试时常用的一组:

用例输入预期输出
不重叠<br>A 1 08:00-09:00<br>B 2 10:00-11:00A 60 / B 60
高优先级局部覆盖<br>A 1 09:00-10:00<br>B 5 09:30-10:30A 30 / B 60
同优先级重叠<br>A 3 09:00-10:30<br>B 3 09:30-11:00A 90 / B 30

第三种情况里,09:30到10:30这60分钟因为同优先级且A先出现,归属A。B能拿到的只有10:30到11:00的30分钟。

跑完这三组,基本可以确认主逻辑没大问题。如果题目要求后到覆盖,直接把覆盖判断里的<改成<=,再跑一遍第三种用例,输出会变成A60、B60。

4. 当数据量变大时,我用区间减法来替代扫描

4.1 区间减法的整体思路

分钟扫描虽然稳,但如果你参加的是某些在线练习平台的加强版,把一天1440分钟改成7天、30天,或者把时间粒度从分钟改成秒,扫描法就会变成1440 * 天数 * 记录数,虽然多数时候还是能跑,但心里会没底。

这时候可以升级成区间减法。

思路是:

  • 先按优先级降序处理App,优先级相同就按原来顺序处理。
  • 用一个occupied列表保存“已经被更高优先级或同优先级先出现App占用的时间区间”。
  • 对当前App的每个时间段,从里面减掉occupied覆盖掉的部分。
  • 剩下的就是当前App真正能拿到的时间。

这样每一步只处理区间,不依赖1440这个常数。

4.2 合并占用区间并做差集

实现差集前,先把占用区间合并成互不重叠的区间,这样减法逻辑会清晰很多。

static List<int[]> merge(List<int[]> intervals) { intervals.sort(Comparator.comparingInt(a -> a[0])); List<int[]> res = new ArrayList<>(); for (int[] v : intervals) { if (res.isEmpty() || v[0] > res.get(res.size() - 1)[1]) { res.add(v); } else { res.get(res.size() - 1)[1] = Math.max(res.get(res.size() - 1)[1], v[1]); } } return res; }

然后对每个当前区间做减法。核心逻辑是拿一个指针now从当前区间起点往后移动,遇到占用区间就跳过,没有被占用的部分保存到结果里:

static List<int[]> subtract(List<int[]> ranges, List<int[]> blocked) { List<int[]> free = new ArrayList<>(); for (int[] cur : ranges) { int now = cur[0]; for (int[] b : blocked) { if (b[1] <= now) { continue; } if (b[0] >= cur[1]) { break; } if (b[0] > now) { free.add(new int[]{now, Math.min(cur[1], b[0])}); } now = Math.max(now, b[1]); if (now >= cur[1]) { break; } } if (now < cur[1]) { free.add(new int[]{now, cur[1]}); } } return free; }

处理流程就是:

List<int[]> occupied = new ArrayList<>(); for (App app : 按优先级排序后的App列表) { List<int[]> free = subtract(app.ranges, occupied); appRealTime = sum(free); occupied = merge(occupied + app.ranges); }

第一次看这段可能觉得绕,但如果你能把1440数组那版先写通,再来理解这个减法,会发现本质一模一样:扫描法是一分钟一分钟判断,减法是一个区间一个区间判断,覆盖优先级规则完全相同。

4.3 什么时候必须用区间减法

我的建议是:机考优先写1440数组版,如果确认题目出现下面任一情况,再切到区间减法。

  • 时间范围不是一天,而是多天累计。
  • 时间粒度精确到秒,数组长度会变成86400,虽然还能跑,但多组用例叠加会让常数变大。
  • 输入记录特别多,每个App的时间段特别碎,导致扫描法的总操作数接近千万级。

不过,华为OD机考C卷的“手机App防沉迷系统”,绝大多数版本都是一天时间、分钟粒度,1440数组版足够。区间减法更多是帮自己加深对区间覆盖的理解。

5. 机考现场最容易丢分的几个细节与双机位避坑

5.1 时间解析:少写一个补零也能跑

很多同学习惯性认为输入一定是08:00这种两位小时,但机考用例可能给你8:00。Integer.parseInt("8")没有问题,所以不需要额外补零。真正容易出错的是用String.split("-")时,如果时间字符串里混入了别的东西,例如全角冒号或中文横线,就会解析失败。考场输入一般是标准半角符号,但如果心里不踏实,可以先打印一遍解析出的start和end,确认没问题再往下写。

5.2 覆盖方向写反是最大陷阱

代码里最关键的判断是:

apps.get(owner[minute]).priority < app.priority

这句表达的是“当前App优先级更高时替换”。不少人一紧张会写成:

apps.get(owner[minute]).priority > app.priority

结果变成低优先级覆盖高优先级,越覆盖越错。我自己就犯过一次,查了半天才反应过来是大于小于写反了。机考现场没有太多时间调试,所以强烈建议写完覆盖逻辑后,立刻用一个三行的小用例自测:

A 1 09:00-10:00 B 5 09:30-10:30

如果输出不是A 30 / B 60,说明覆盖方向有问题。

5.3 输出顺序和排序要求

有的变体题目要求按“总时长从高到低”输出,有的按“第一次出现顺序”,还有的按“App名称字典序”。这三种我都在不同题库里见过类似版本。读题时先圈出“按什么顺序输出”,比什么都重要。我的模板里默认是按第一次出现顺序,因为这是常见版本,但你在考场一定要以题面为准。如果要求排序输出,可以把最后打印部分的循环换成一个排序后的列表。

5.4 双机位考试的节奏建议

双机位环境下,避免频繁转头看副机位或手机,容易影响思路。比较好的节奏是:

  1. 先花5分钟把题面里的输入输出约定抄到草稿纸上,列出样例。
  2. 用10分钟写一个最直接的解法,我的选择是1440数组扫描。
  3. 写完后先跑题面样例,再跑自己造的三组边界用例。
  4. 确认输出格式完全一致后,最后再去做额外优化。

千万不要一开始就追求写区间减法。机考看的是“能不能在规定时间内跑通全部用例”,不是“谁的数据结构更高级”。稳,比炫重要。

6. 沉淀一下:从这道题看区间类题目的通用套路

6.1 区间题三板斧:扫描线、差分数组、按优先级贪心

“手机App防沉迷系统”是一个带优先级的区间覆盖题。把优先级去掉,它就是一个普通的区间合并题;把覆盖规则改成“计数叠加”,它又可以用差分数组来做。所以我习惯把这类题归成三个方向:

  • 扫描线:把所有区间当成事件点,开始点+1,结束点-1,适合求最大重叠数。比如多个直播时间段,问同一时间最多有几个直播。
  • 差分数组:适合对一段连续区间做批量加减,最后统计每个点被覆盖多少次。
  • 按优先级贪心:像防沉迷系统这样,某个区间被更高优先级覆盖后属于高优先级,就按优先级从高到低处理,使用差集。

这一题最妙的地方在于,三种思路都能解,但只有“按优先级贪心”最贴近业务语义。你把三种都写一遍,以后再遇到区间类题目,第一反应就会快很多。

6.2 判断两个区间是否重叠的写法

这个细节看起来很基础,但真的能卡住人。两个左闭右开区间[a1, a2)和[b1, b2)重叠的判断条件是:

a1 < b2 && b1 < a2

而不是a1 <= b2 && b1 <= a2。因为左闭右开区间里,a2和b1相等时,两个区间首尾相接但不重叠。比如09:00-10:00和10:00-11:00,重叠分钟数为0。很多合并错误都是从这里开始的。

如果你决定写区间减法版本,请务必把这句话刻在脑子里。

6.3 顺带可以练手的相似方向

考完防沉迷系统,我建议顺带刷几类相似题:

  • 会议室预订:给定会议开始结束时间,判断是否需要额外会议室。
  • 系统资源占用:多个进程在不同时间段占用CPU,求峰值。
  • 直播热度统计:多个主播的直播时段,求同时在线人数最多的时间点。
  • 合并时间段:把重叠的时间段合并成最少数量。

这几类题背后的数据结构无非就是区间数组、优先级比较、扫描事件点。把防沉迷这一题吃透,等于把这些题的主干逻辑都过了一遍。

我个人的体会是,机考题目虽然经常披着各种业务外衣,但脱掉外衣后全是数据结构基本功。与其背诵“某道题的标准答案”,不如训练自己“遇到区间覆盖先想归属,再想优化”的条件反射。希望这篇整理能让你少走一点我当时走过的弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 2:19:51

AI视频变现实战:漫剧与纪录片两条路线从0到1全拆解

1. 从“能生成”到“能变现”&#xff1a;AI视频赛道的真实门槛在哪里过去一年多&#xff0c;我身边做内容的朋友几乎都经历过同一个心理曲线&#xff1a;第一次看到AI生成的视频时觉得“这玩意儿太牛了”&#xff0c;自己上手试了几天发现“好像也就那样”&#xff0c;再刷到别…

作者头像 李华
网站建设 2026/10/17 10:30:55

宝信PLC在梅钢国产化替代实战:方案设计、迁移与调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 12:23:16

2026论文AIGC检测通关指南:免费查重渠道与降AI工具实测

每年到这个时间点&#xff0c;后台私信就会被论文相关问题塞满。往年大家问的还都是“怎么降重”“知网查重怎么收费”&#xff0c;今年风向彻底变了——十个里有七个在问“AIGC检测到底怎么过”“有没有免费的AI查重”&#xff0c;剩下三个已经在追问“降AIGC的工具到底哪个靠…

作者头像 李华
网站建设 2026/10/17 12:23:06

SSM+Vue旅游景点售票系统毕设全攻略:从数据库设计到并发扣减

又到毕设季&#xff0c;“ssmvue旅游景点售票系统”这个题目&#xff0c;几乎是计算机类专业里最稳的选择之一。别看名字平淡&#xff0c;它把后端 SSM、前端 Vue、数据库设计、论文写作全串在一条线上&#xff0c;对要毕业设计的同学来说&#xff0c;是投入产出比很高的项目。…

作者头像 李华
网站建设 2026/10/17 12:23:16

大学生网络安全竞赛从零参赛指南:CTF方向、刷题方法与团队复盘

我第一次接触网络安全竞赛&#xff0c;是在大一社团招新时看到CTF三个字母&#xff0c;第一反应是“这是不是黑客比赛&#xff1f;”当时我连flag是什么都不知道&#xff0c;更不用说搞清楚Web和Pwn的区别。后来真正入坑才明白&#xff0c;网络安全竞赛并不神秘——它只是把安全…

作者头像 李华
网站建设 2026/10/21 14:03:35

微服务下的Java分销系统源码:架构拆解与返佣避坑

简介&#xff1a;一套基于微服务架构的Java分销管理系统源码&#xff0c;面向具备一定Java基础、希望学习微服务落地实践或需要搭建分销业务后台的开发人员与学习者。源码包共1643个文件&#xff0c;压缩包大小约15.02MB&#xff0c;文件类型丰富&#xff0c;既包含Java后端代码…

作者头像 李华