华为OD机试经典题解:贪心算法与优先队列实现最大任务调度
1. 项目概述与核心需求解析最近在准备华为OD机试的同学们应该对“可以处理的最大任务”这道题不陌生了。它作为C卷的经典题目频繁出现在各种机试真题和模拟题中是检验候选人贪心算法和排序思维的一道“试金石”。这道题的核心远不止是写对一个排序和循环那么简单它背后考察的是你如何将现实中的任务调度问题抽象成一个可计算的数学模型并选择最高效的策略去求解。很多朋友第一次做可能会觉得思路有点绕或者能通过样例但总有几个测试点过不去。今天我就结合自己刷题和带新人的经验把这道题从题意理解、思路推导到代码实现的每一个细节以及那些容易踩坑的地方给大家掰开揉碎了讲清楚。无论你是刚开始准备OD机试的新手还是想巩固贪心算法基础的同学这篇文章都能让你对这类问题有一个透彻的理解。简单来说题目会给你一系列任务每个任务都有两个关键属性最晚开始时间和所需处理时长。你拥有一台机器它在时间0启动并且处理任务时是独占的即同一时间只能处理一个任务。你的目标是如何安排这些任务的执行顺序使得在任务不超时即任务的实际开始时间不晚于其最晚开始时间的前提下这台机器能够处理的任务数量最大化。这听起来就像一个高难度的“时间管理大师”挑战你需要在一堆截止日期和耗时各不相同的待办事项中做出最优的取舍。2. 问题本质与算法思路拆解2.1 问题建模从生活场景到算法抽象我们可以把这个问题映射到一个非常生活化的场景你是一名学生今天有若干门作业要写每门作业都有一个“最晚开始动笔时间”比如数学作业晚上8点前必须开始写和“需要连续写作的时长”比如需要写1小时。你从放学后时间0开始一次只能专心写一门作业。你怎么安排写作业的顺序才能完成尽可能多的作业并且保证每门作业都在它的“最晚开始时间”之前动笔理解了这个场景问题的两个核心约束就非常清晰了任务独占性机器/你同一时刻只能处理一个任务。时间约束每个任务必须在它的最晚开始时间之前开始执行。我们的目标是最大化任务数量而不是总耗时或其他指标。这直接提示我们这可能是一个贪心算法可以解决的问题。贪心算法的精髓在于每一步都做出当前看起来最优的选择并希望这种局部最优能导致全局最优。对于任务调度问题常见的贪心策略有按截止时间最早、按处理时间最短、按开始时间最早等。我们需要找到适合本题的贪心策略。2.2 思路推导为什么是“截止时间最晚开始时间 最小堆”直接思考所有任务的排列组合会非常复杂阶乘级。我们必须找到一个可以逐步构建最优解的规律。经过分析一个行之有效的策略是按最晚开始时间升序排序优先考虑那些“截止时间”更紧迫的任务。这是一个很自然的想法把最着急的事情先纳入考虑范围。使用一个“当前时间”变量和一个小顶堆优先队列current_time记录机器已经安排到的时刻。小顶堆用来动态维护当前“已选择要执行”的任务的处理时长。堆顶永远是已选任务中处理时间最长的那个。算法的核心流程如下我们按顺序遍历排序后的任务。对于每个任务我们先尝试把它加入执行计划即假设我们现在开始执行它那么current_time需要增加这个任务的耗时。我们将这个耗时放入堆中。加入后我们检查更新后的current_time是否超过了这个任务的最晚开始时间。如果没有超过太好了这个任务可以被顺利安排我们继续处理下一个任务。如果超过了说明我们当前的选择包含了这个新任务已经导致它无法在其截止时间前开始了。这时贪心的策略就体现出来了——我们丢弃掉当前已选任务中“处理时间最长”的那个任务也就是小顶堆的堆顶元素。因为丢弃最耗时的任务能为时间表腾出最多的空闲最有可能让后续更多短任务被加入。从堆中弹出堆顶最长耗时。current_time减去这个被丢弃任务的耗时。遍历完所有任务后堆的大小就是我们能处理的最大任务数量。这个思路为什么有效关键在于当我们发现加入新任务导致超时时我们选择“牺牲”掉已选任务中最“费时”的一个而不是新任务本身。因为新任务可能是短任务而我们已经安排的一个长任务虽然截止时间可能更晚但它占用了大量时间导致后续任务无法插入。通过维护一个“已选任务耗时最大堆”我们总能及时剔除那个“最不划算”的长任务从而为更多短任务腾出空间最终实现任务数量的最大化。这是一种“以数量换时间”的贪心策略。注意这里有一个非常关键的细节也是很多初次接触此算法的同学疑惑的点。我们排序的依据是任务的“最晚开始时间”但堆里维护的是“处理时长”。排序保证了我们按截止时间的紧迫性来扫描任务而堆帮助我们动态优化已选任务的集合确保总耗时尽可能小。两者结合缺一不可。3. 核心数据结构与C实现详解3.1 数据结构选择vector,pair,priority_queue在C中实现上述算法我们需要选择合适的数据结构来高效地表达和操作数据。任务表示每个任务包含两个整数最晚开始时间deadline和处理时长duration。使用std::pairint, int非常合适或者定义一个简单的struct Task。为了排序方便我们使用pair并约定first为最晚开始时间second为处理时长。任务列表使用std::vectorstd::pairint, int tasks来存储所有任务。“已选任务耗时”容器我们需要一个能快速获取最大值、插入和删除最大值的数据结构。std::priority_queue优先队列完美符合要求。默认的priority_queueint是大顶堆堆顶最大而我们需要的是能快速访问最大值的容器以便丢弃最长任务所以直接使用大顶堆即可。在代码逻辑中这个堆里存放的就是已被纳入当前计划的任务的duration。3.2 代码实现逐行解析下面给出完整的C实现并附上详细注释。#include iostream #include vector #include algorithm #include queue // 用于priority_queue using namespace std; int maxTasks(vectorpairint, int tasks) { // 1. 特殊情况处理如果任务列表为空直接返回0 if (tasks.empty()) { return 0; } // 2. 按任务的最晚开始时间deadline进行升序排序 // 使用lambda表达式定义排序规则比较pair的first元素 sort(tasks.begin(), tasks.end(), [](const pairint, int a, const pairint, int b) { return a.first b.first; // 按最晚开始时间从小到大排 }); // 3. 初始化一个最大堆优先队列用于存储已选择任务的耗时 // priority_queue默认是最大堆堆顶元素最大 priority_queueint max_heap; // current_time 记录当前时间线进展到的位置 int current_time 0; // 4. 遍历排序后的每一个任务 for (const auto task : tasks) { int deadline task.first; int duration task.second; // 4.1 尝试将当前任务加入计划 current_time duration; // 假设执行它当前时间推进 max_heap.push(duration); // 将其耗时放入堆中 // 4.2 检查加入后是否导致当前任务超时当前时间 该任务最晚开始时间 if (current_time deadline) { // 如果超时说明当前计划不可行需要“牺牲”一个任务 // 贪心策略丢弃当前已计划任务中耗时最长的那个即堆顶任务 int longest_duration max_heap.top(); // 获取最长耗时 max_heap.pop(); // 将其从计划中移除 current_time - longest_duration; // 当前时间回退这个任务的耗时 } // 如果没超时则什么也不做继续下一个任务 } // 5. 遍历结束后堆中剩余的任务数就是能处理的最大任务数量 return max_heap.size(); } int main() { // 示例输入任务列表 {最晚开始时间, 处理时长} vectorpairint, int tasks {{5, 3}, {3, 4}, {2, 1}, {10, 2}, {7, 5}}; int result maxTasks(tasks); cout 可以处理的最大任务数量是: result endl; // 对于示例最优解是执行 {2,1}, {5,3}, {10,2} 这三个任务结果为3。 return 0; }关键代码段解析sort(tasks.begin(), tasks.end(), [](...) {...}) 使用Lambda表达式进行自定义排序确保任务按最晚开始时间从小到大处理。这是贪心策略的第一步也是正确性的基础。priority_queueint max_heap 声明一个存储int类型的最大堆。这个堆是我们进行“任务替换”优化的核心工具。循环体内的if (current_time deadline) 这是算法的灵魂所在。它实现了“尝试-检查-回退”的贪心逻辑。current_time模拟了真实的时间流逝而deadline是硬性约束。一旦违反约束就立刻进行修正。max_heap.top()和max_heap.pop() 当需要修正时我们取出并移除当前计划中最耗时的任务。因为堆顶就是最大值所以这个操作是O(log n)的非常高效。return max_heap.size() 最终所有经历过“加入”和“可能被剔除”筛选后仍留在堆里的任务就是我们的最优解集合其大小即为答案。3.3 复杂度分析时间复杂度O(n log n)。排序操作sort的时间复杂度为O(n log n)。接下来对n个任务进行遍历每次遍历中堆的插入(push)和弹出(pop)操作复杂度均为O(log k)其中k是堆的大小k ≤ n。因此遍历部分的总复杂度也是O(n log n)。综合来看主导因素是排序整体为O(n log n)。这对于机试中常见的数据规模n ≤ 10^5是完全可行的。空间复杂度O(n)。主要用于存储任务列表tasks和优先队列max_heap。在最坏情况下所有任务都可能被加入堆中尽管随后可能被弹出因此空间复杂度为O(n)。4. 算法正确性证明与思维延伸4.1 贪心选择性质的简要证明为什么丢弃耗时最长的任务是最优的我们可以用交换论证的思想来理解。 假设在某个时刻我们有一个任务集合S当前时间current_time超过了新任务t_new的截止时间。为了容纳t_new我们必须从S中至少移除一个任务。设被移除的任务是t_out。 我们的目标是让current_time减少得尽可能多这样不仅能让t_new满足截止时间也为后续任务留出更多空间。current_time的减少量等于t_out.duration。 因此为了最大化减少量自然应该选择S中duration最大的任务即t_out argmax_{t in S} t.duration。这就是我们算法中选择堆顶任务的原因。这个选择保证了在移除一个任务后新的时间表是“最宽松”的从而为最大化任务总数提供了可能。4.2 与类似问题的对比这道题很容易和另一个经典贪心问题“活动选择问题”Activity Selection Problem混淆。活动选择问题是给定一系列活动的开始和结束时间求能参加的不冲突活动的最大数量。它的贪心策略是按结束时间排序每次选择结束最早且不与之前选择冲突的活动。两者的核心区别在于活动选择活动的“时长”是固定的开始到结束选择了一个活动就占用了那整段时间。冲突是指时间区间重叠。本问题最大任务处理任务有“最晚开始时间”和“处理时长”但实际开始时间可以提前。冲突不是看区间重叠而是看如果按某个顺序执行是否每个任务都能在其截止时间前开始。它更像是一个带截止时间的单机调度问题。理解这个区别有助于你在遇到新题时快速定位算法模型。5. 常见错误与实战调试技巧5.1 典型错误案例排序依据错误错误做法按处理时长(duration)排序优先做短任务。反例任务列表为[{100, 90}, {5, 4}]。按短任务优先会先做{5,4}耗时4当前时间4未超时然后做{100,90}耗时90当前时间94未超时。结果是2个任务。但最优解其实是只做{100,90}这一个任务吗不这里两个都能做。但这个策略在面对更复杂的例子时会失败因为它没有考虑截止时间的紧迫性。错误做法按最晚开始时间排序但超时后丢弃的是新任务而不是已选中最长任务。这会导致无法达到最大任务数。忽略边界条件输入任务列表为空时函数应返回0。任务的最晚开始时间可能小于其处理时长这种任务本身就不可能完成因为即使在时间0开始处理完也超过了它的最晚开始时间。我们的算法能正确处理这种情况吗答案是肯定的。例如任务{2, 5}deadline2,duration5。算法会先将其加入current_time5然后发现5 2于是从堆中弹出它duration5current_time回退到0。这个任务最终不会被计入结果。这符合逻辑。current_time初始化与更新错误current_time必须初始化为0代表机器从时间0开始可用。每次加入任务时是 duration丢弃时是- duration。顺序不能错。5.2 调试与测试策略在机试或自己练习时如何快速验证代码的正确性设计小规模测试用例不要只依赖题目给的样例。用例1tasks {} 预期结果0。用例2tasks {{1, 100}} 预期结果0因为100 1无法完成。用例3tasks {{5, 1}, {5, 1}, {5, 1}} 预期结果3三个短任务都可以在时间5之前完成。用例4tasks {{2, 3}, {4, 2}, {6, 5}}。手动推导排序后为[{2,3}, {4,2}, {6,5}]。加入{2,3}: time3, heap{3}, 32? 是丢弃3time0, heap{}。加入{4,2}: time2, heap{2}, 24? 否。加入{6,5}: time7, heap{2,5}, 76? 是丢弃5time2, heap{2}。最终heap大小1。最优解是只执行{4,2}这一个任务。你可以验证其他顺序都不如这个好。使用priority_queue的调试技巧如果你想在调试时查看堆里的内容需要注意的是priority_queue没有迭代器。一个简单的调试方法是准备一个辅助向量在每次操作堆后将堆的内容复制出来打印但这会破坏堆仅用于调试。// 非生产代码仅用于调试理解过程 void debugHeap(priority_queueint heap) { // 注意这里传值不改变原堆 vectorint v; while (!heap.empty()) { v.push_back(heap.top()); heap.pop(); } cout Heap contents: ; for (int d : v) cout d ; cout endl; }对比暴力枚举结果针对极小规模n当n很小比如10时可以写一个暴力枚举所有任务子集和排列顺序的程序计算最大可完成任务数用来验证贪心算法的结果。这是验证算法正确性的终极方法。5.3 机试实战注意事项输入输出格式华为OD机试通常是标准输入输出。确保你的main函数能正确读取数据。题目可能先给一个n然后n行每行两个整数。务必处理好输入格式。int main() { int n; cin n; vectorpairint, int tasks(n); for (int i 0; i n; i) { cin tasks[i].first tasks[i].second; // 假设输入是 deadline duration } cout maxTasks(tasks) endl; return 0; }变量命名清晰在紧张的考试环境中使用deadline和duration这样清晰的变量名远比a和b有助于你理清思路避免逻辑错误。优先队列的声明记住priority_queueint默认是最大堆。如果需要最小堆应该声明为priority_queueint, vectorint, greaterint。本题我们需要的正是最大堆。时间与空间考虑本题O(n log n)的算法足够应对大数据量。如果遇到特别大的n确认没有使用O(n^2)的算法即可。这道“可以处理的最大任务”题完美地融合了排序、贪心、优先队列这三个重要的知识点。理解其背后的“按截止时间扫描”和“替换最长任务”的贪心思想不仅是为了解这一道题更是为你解决一整类调度优化问题提供了有力的工具。在平时的练习中多问几个“为什么”多构造几个边缘用例去测试你的算法思维和代码实现能力才会得到扎实的提升。

相关新闻

北数云智能体平台:AI驱动的专业场景解决方案

北数云智能体平台:AI驱动的专业场景解决方案

1. 北数云智能体平台内测深度体验 作为一名长期关注AI应用落地的技术博主,我有幸获得了北数云平台的内测资格。经过一周的深度使用,这个聚焦"智能体"概念的AI云平台确实给我带来了不少惊喜。与市面上常见的通用型AI工具不同,北数云…

2026/7/27 8:13:25阅读更多 →
FlexRay FTU实战:零CPU干预数据通道配置与调试指南

FlexRay FTU实战:零CPU干预数据通道配置与调试指南

1. 项目概述与核心价值 如果你正在开发下一代汽车电子控制单元(ECU),或者设计一个要求高可靠性和确定性的工业实时控制系统,那么FlexRay这个名字你一定不陌生。它早已不是实验室里的概念,而是大量应用在高端汽车的底盘…

2026/7/27 8:13:25阅读更多 →
AI生成内容检测与文本降重双目标优化方案

AI生成内容检测与文本降重双目标优化方案

1. 项目背景与核心痛点 在学术写作和内容创作领域,AI生成内容检测和文本重复率问题正成为两大核心挑战。去年某高校研究生院的内部数据显示,超过60%的学位论文初稿存在AI生成内容嫌疑,同时有45%的文本重复率超过警戒线。这两个问题往往相伴而…

2026/7/27 8:11:25阅读更多 →
Zend-Expressive性能优化实战:从代码到部署的全链路加速方案

Zend-Expressive性能优化实战:从代码到部署的全链路加速方案

Zend-Expressive性能优化实战:从代码到部署的全链路加速方案 【免费下载链接】zend-expressive PSR-15 middleware in minutes! 项目地址: https://gitcode.com/gh_mirrors/ze/zend-expressive Zend-Expressive作为一款轻量级PSR-15中间件框架,以…

2026/7/27 11:36:33阅读更多 →
PytorchNetHub项目概览:为什么它是Pytorch实践者的必备资源库

PytorchNetHub项目概览:为什么它是Pytorch实践者的必备资源库

PytorchNetHub项目概览:为什么它是Pytorch实践者的必备资源库 【免费下载链接】PytorchNetHub 项目注释论文复现算法竞赛Pytorch实践LeetCodeVLM预训练 项目地址: https://gitcode.com/gh_mirrors/py/PytorchNetHub PytorchNetHub是一个集项目注释、论文复现…

2026/7/27 11:36:33阅读更多 →
如何用Newcar快速创建你的第一个Canvas动画?5分钟上手教程

如何用Newcar快速创建你的第一个Canvas动画?5分钟上手教程

如何用Newcar快速创建你的第一个Canvas动画?5分钟上手教程 【免费下载链接】newcar 🖌️ [WIP] Modern JavaScript Canvas Library 项目地址: https://gitcode.com/gh_mirrors/ne/newcar Newcar是一款现代JavaScript Canvas库,能够帮助…

2026/7/27 11:36:33阅读更多 →
从DRV8703D-Q1评估板入门电机驱动:硬件解析与实战调试

从DRV8703D-Q1评估板入门电机驱动:硬件解析与实战调试

1. 项目概述与核心价值在嵌入式系统、工业自动化乃至消费电子领域,直流有刷电机的驱动与控制是一个基础且高频的需求。无论是机器人关节的精准定位、智能家居窗帘的平稳开合,还是汽车座椅的自动调节,其背后都离不开一个高效、可靠的电机驱动电…

2026/7/27 11:36:33阅读更多 →
深入解析LMK05028 DPLL核心寄存器配置与时钟同步实战

深入解析LMK05028 DPLL核心寄存器配置与时钟同步实战

1. LMK05028与DPLL:时钟系统的“心脏”与“大脑”在高速通信、数据中心交换、雷达系统以及精密测试测量设备中,一个稳定、纯净且同步的时钟信号,是整个系统正常运行的基石。你可以把它想象成一场交响乐演出,如果指挥家的节拍器不准…

2026/7/27 11:36:33阅读更多 →
告别手酸!阴阳师自动化脚本的3种智能模式解放你的双手

告别手酸!阴阳师自动化脚本的3种智能模式解放你的双手

告别手酸!阴阳师自动化脚本的3种智能模式解放你的双手 【免费下载链接】OnmyojiAutoScript Onmyoji Auto Script | 阴阳师脚本 项目地址: https://gitcode.com/gh_mirrors/on/OnmyojiAutoScript 你是否厌倦了在阴阳师百鬼夜行中重复点击屏幕,手指…

2026/7/27 11:34:33阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/27 1:14:34阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/27 1:14:52阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/27 1:14:56阅读更多 →
SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

1. 项目概述:从寄存器手册到实战指南 如果你手头有一份类似德州仪器(TI)TMS320x240xA系列DSP的SPI模块技术手册,看着里面密密麻麻的寄存器位定义、时序图和公式,是不是感觉头大?这份资料虽然权威&#xff0…

2026/7/27 0:00:24阅读更多 →
【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/27 0:00:24阅读更多 →
2007-2023年各市区县生态文明建设示范区DID

2007-2023年各市区县生态文明建设示范区DID

数据简介 自改革开放以来,我国依赖高投入、高资源消耗和高污染等传统发展模式实现了经济短期内的快速增长, 然而这也导致了严重的生态环境危机。因此,国家有力于推动企业高质量经济发展,协同生态保护的方针,从而从201…

2026/7/27 0:00:24阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/25 23:03:25阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/26 19:05:21阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/26 19:05:21阅读更多 →