华为OD机试真题解析:打印任务排序与优先级队列实战

📅 2026/7/27 6:16:30
华为OD机试真题解析:打印任务排序与优先级队列实战
1. 项目概述从一道机试真题看队列与优先级的实战应用最近在辅导几位准备华为OD机试的朋友他们不约而同地提到了“打印任务排序”这道题。这道题在E卷中出现的频率不低而且它非常经典几乎涵盖了数据结构入门阶段最核心的几个概念队列、优先级、模拟。很多朋友第一次看到题目描述时会觉得“这不就是个排队打印吗”但真正上手写代码才发现里面藏着不少细节和“坑”。今天我就结合自己当年备考和后来面试别人的经验把这道题从里到外拆解一遍不仅给出C、Java、Python、C、JS五种语言的参考实现更重要的是分享解题思路的构建过程、代码调试中的常见陷阱以及如何将这道题的思维模型应用到更广泛的场景中去。简单来说“打印任务排序”模拟的是一个有优先级的打印队列。系统里有一堆打印任务每个任务有自己的优先级通常是1-9的数字9最高。打印机会从队列头部取任务但如果队列中有比当前队头任务优先级更高的任务则队头任务会被重新放到队尾。我们的目标是给定一个初始任务序列和某个特定任务的位置比如你在队列中提交的那个任务计算出你这个任务是第几个被打印完成的。这听起来就像在银行排队但有个VIP高优先级可以随时插队我们需要知道自己到底要等多久。这道题的价值在于它绝不仅仅是一道“算法题”。理解它你就能理解操作系统中的进程调度如优先级调度算法、消息队列中间件如RabbitMQ的优先级队列、乃至任何需要处理“排队但可插队”业务逻辑的场景如客服工单系统、游戏中的技能释放队列。接下来我们就从最核心的思路拆解开始。2. 核心思路拆解如何将生活场景抽象为代码模型面对这个问题第一步不是急着写代码而是把题目描述的场景用我们熟悉的数据结构清晰地建模出来。这个过程是解题的关键也是面试官最看重的“分析能力”。2.1 问题重述与关键点捕捉我们先把题目用更技术化的语言描述一下输入一个代表打印任务优先级的数组以及一个整数代表我们关心的那个任务在初始队列中的位置索引。规则打印机每次检查队列头的任务。如果队列中存在比队头任务优先级更高的任务则将队头任务移动到队列末尾。如果队头任务就是当前队列中优先级最高的或之一则打印该任务即从队列中移除计时器加1。输出我们关心的那个任务被打印时计时器的值即它是第几个被打印的。这里有几个容易混淆的关键点必须一开始就厘清“优先级更高”指的是优先级数值更大。题目通常约定优先级范围是1-99最高。如果有多个任务优先级相同则按原始顺序处理先到先得。“关心任务的位置”这个位置是初始队列中的索引。一旦队列开始按照规则移动任务这个任务在队列中的实际位置是动态变化的。我们不能只跟踪索引必须跟踪这个任务本身。“何时计时”只有当一个任务被真正打印弹出队列时才计数一次。移动任务到队尾是不计时的。2.2 数据结构选型为什么是队列核心数据结构的选择几乎是唯一的队列Queue。因为打印任务天然就是先来后到FIFO的而“将队头移到队尾”这个操作正是队列的经典操作。在代码中我们通常用双端队列Deque或普通队列来模拟。但这里有一个关键我们需要在每一步判断“队列中是否存在比队头优先级更高的任务”。如果每次判断都遍历整个队列时间复杂度会是O(n^2)在任务较多时效率低下。一个常见的优化思路是额外维护一个优先级排序的列表例如一个按优先级降序排列的数组或最大堆用来快速判断当前队头是否是最高优先级。然而对于机试场景数据量通常不会太大为了代码清晰和易于实现我更推荐第一种直观方法在每一步中取出队头任务后遍历当前队列中剩余的所有任务检查是否有优先级更高的。这种方法虽然理论复杂度高但代码直观不易出错完全满足机试要求。在实际面试中先给出清晰正确的解法再讨论优化是更稳妥的策略。2.3 算法流程设计模拟法基于以上分析我们可以梳理出清晰的算法步骤这是一个典型的模拟算法初始化将任务列表和优先级列表存入一个队列。队列中的元素最好是一个pair或小型对象同时包含优先级和初始索引。因为我们需要在移动任务时始终能识别出哪个是我们关心的“特殊任务”。初始化一个计时器time 0。模拟循环当队列不为空时 a. 取出当前队头任务包括其优先级和初始索引。 b.检查遍历当前队列中的所有任务即刚取出的队头之后的所有任务判断是否存在优先级高于队头任务的任务。 c.判断与操作 * 如果存在更高优先级的任务将队头任务重新放回队列的末尾。 * 如果不存在更高优先级的任务即队头任务就是当前最高优先级说明这个任务可以打印了。 * 计时器加一time。 * 检查这个被打印的任务的初始索引是否等于我们关心的那个位置target_index。 * 如果相等模拟结束返回当前的time。 * 如果不相等则继续循环处理下一个队头任务。这个流程就像是一个尽职的打印机管理员不断检查队头不是最高优先级就丢到后面重新排队是最高的就打印并记录打印顺序。3. 多语言代码实现与逐行分析理解了核心思路我们来看代码实现。我会用五种常见的编程语言分别实现并重点分析每种语言实现时的特有技巧和注意事项。为了清晰我们假设输入已经处理好priorities是优先级数组location是关心任务的初始索引从0开始。3.1 C 实现C的标准模板库STL提供了强大的queue和deque容器非常适合本题。#include iostream #include queue #include vector using namespace std; int printQueue(vectorint priorities, int location) { queuepairint, int q; // 队列元素优先级, 初始索引 int time 0; // 初始化队列 for (int i 0; i priorities.size(); i) { q.push({priorities[i], i}); } while (!q.empty()) { auto current q.front(); // 取出队头 q.pop(); bool hasHigherPriority false; // 检查队列中剩余任务是否有更高优先级 // 注意这里需要遍历当前队列中的所有元素。 // 一种方法是临时将队列内容转存到另一个队列或向量中检查但更高效的方法是直接遍历队列C的queue没有迭代器不太方便。 // 我们采用一个简单方法在取出队头时我们已经知道它的优先级。现在我们需要检查当前队列q中是否有比current.first更高的优先级。 // 由于queue不支持直接遍历我们可以利用一个临时队列来辅助检查和恢复。 queuepairint, int temp q; while (!temp.empty()) { if (temp.front().first current.first) { hasHigherPriority true; break; } temp.pop(); } if (hasHigherPriority) { // 存在更高优先级当前任务重新入队尾 q.push(current); } else { // 当前任务可以打印 time; // 检查是否是我们关心的任务 if (current.second location) { return time; } // 否则该任务被丢弃已打印继续循环 } } return time; // 理论上一定会找到这里为了语法完整 }C实现要点分析使用pairint, int将优先级和初始索引绑定在一起作为一个整体在队列中移动这是跟踪特定任务的关键。队列遍历的麻烦C的std::queue是一个容器适配器设计上不提供迭代器这是为了强调其FIFO的抽象。我们检查更高优先级任务时需要复制一个临时队列temp来遍历。虽然这增加了一点空间开销但代码逻辑非常清晰。在机试中清晰比极致的优化更重要。auto关键字auto current q.front()让代码更简洁尤其在模板类型复杂时。边界情况循环最终一定会返回因为目标任务一定在队列中。最后一行return time只是为了满足函数返回值要求。注意上面的遍历检查方法因为每次都要复制队列效率不是最优。一个常见的优化是在初始化时就用deque代替queue因为deque支持迭代器可以直接用max_element算法来查找最高优先级。但正如之前所说对于机试清晰第一。如果追求效率可以这样写#include deque #include algorithm // ... dequepairint, int dq; // ... 初始化dq while (!dq.empty()) { auto current dq.front(); dq.pop_front(); // 使用算法检查剩余部分是否有更高优先级 if (any_of(dq.begin(), dq.end(), [](const pairint, int p) { return p.first current.first; })) { dq.push_back(current); } else { time; if (current.second location) return time; } }3.2 Java 实现Java中我们可以使用LinkedList实现了Deque接口来模拟队列它提供了丰富的操作方法。import java.util.LinkedList; import java.util.Queue; public class PrintTaskScheduler { // 定义一个内部类来封装任务信息比用数组或List更清晰 static class Task { int priority; int originalIndex; Task(int p, int idx) { this.priority p; this.originalIndex idx; } } public int solution(int[] priorities, int location) { QueueTask queue new LinkedList(); int time 0; // 初始化队列 for (int i 0; i priorities.length; i) { queue.offer(new Task(priorities[i], i)); } while (!queue.isEmpty()) { Task current queue.poll(); boolean hasHigher false; // 检查队列中是否有更高优先级的任务 // 在Java中Queue没有直接提供遍历所有元素的方法除了迭代器。 // 我们可以通过遍历queue的每个元素来判断。 for (Task t : queue) { if (t.priority current.priority) { hasHigher true; break; } } if (hasHigher) { // 重新加入队尾 queue.offer(current); } else { // 打印当前任务 time; if (current.originalIndex location) { return time; } // 否则current任务被移除继续循环 } } return -1; // 根据题意不会走到这里返回-1表示异常 } }Java实现要点分析使用内部类Task这是比使用int[]或ListInteger更好的实践。它使代码语义更清晰current.priority和current.originalIndex一目了然避免了魔法数字索引如arr[0]代表优先级。Queue接口与LinkedList实现LinkedList是一个完美的选择它既实现了Queue接口支持offer、poll等队列操作又实现了Iterable接口允许我们使用增强for循环直接遍历这使得检查更高优先级任务的代码非常简洁。for (Task t : queue)这个遍历是在current被poll()之后进行的遍历的是队列中剩余的任务。这是完全正确的且代码非常易读。命名规范方法名solution是很多OJ系统的默认要求。变量名hasHigher清晰地表达了布尔值的含义。3.3 Python 实现Python的collections.deque双端队列是处理这类问题的利器结合列表推导式代码可以非常简洁。from collections import deque def print_order(priorities, location): 计算指定位置任务的打印顺序 :param priorities: List[int] 任务优先级列表 :param location: int 目标任务的初始位置 :return: int 打印顺序从1开始 # 创建队列元素为(优先级, 初始索引) queue deque([(p, i) for i, p in enumerate(priorities)]) time 0 while queue: current_priority, current_index queue.popleft() # 检查剩余队列中是否有优先级高于当前任务的 # 使用any()函数和生成器表达式非常Pythonic if any(p current_priority for p, _ in queue): # 有更高优先级重新入队 queue.append((current_priority, current_index)) else: # 打印当前任务 time 1 if current_index location: return time # 理论上不会执行到这里 return timePython实现要点分析deque的使用deque.popleft()和deque.append()是O(1)操作效率很高。用deque而不用list是因为list的pop(0)是O(n)操作在队列较长时性能差。列表推导式初始化[(p, i) for i, p in enumerate(priorities)]一行代码就完成了队列的初始化enumerate同时获取索引和值非常优雅。any()函数与生成器表达式any(p current_priority for p, _ in queue)是这段代码的精华。它遍历队列中每个元素的优先级部分p一旦发现一个大于current_priority的就返回True否则返回False。生成器表达式(p current_priority for p, _ in queue)是惰性求值的在找到第一个True后就会停止通常比先构造一个完整的列表再判断更高效。这里的_表示我们不关心索引部分。元组解包current_priority, current_index queue.popleft()直接解包让变量名具有描述性代码清晰。简洁与效率的平衡这段代码在简洁性、可读性和效率上达到了很好的平衡是Python风格的典范。3.4 C语言实现C语言没有内置的队列库需要我们手动实现一个队列这能很好地考察基本功。#include stdio.h #include stdbool.h #define MAX_SIZE 100 // 假设任务数量不超过100可根据题目调整 // 定义任务结构体 typedef struct { int priority; int originalIndex; } Task; // 定义循环队列 typedef struct { Task data[MAX_SIZE]; int front; int rear; } Queue; // 队列初始化 void initQueue(Queue *q) { q-front 0; q-rear 0; } // 判断队列是否为空 bool isEmpty(Queue *q) { return q-front q-rear; } // 入队 bool enqueue(Queue *q, Task task) { if ((q-rear 1) % MAX_SIZE q-front) { return false; // 队列满 } q-data[q-rear] task; q-rear (q-rear 1) % MAX_SIZE; return true; } // 出队 bool dequeue(Queue *q, Task *task) { if (isEmpty(q)) { return false; } *task q-data[q-front]; q-front (q-front 1) % MAX_SIZE; return true; } // 获取队列长度用于遍历 int queueLength(Queue *q) { return (q-rear - q-front MAX_SIZE) % MAX_SIZE; } // 获取队列中指定位置的元素用于遍历检查 Task getElement(Queue *q, int pos) { int index (q-front pos) % MAX_SIZE; return q-data[index]; } // 打印任务排序主函数 int printOrder(int priorities[], int prioritiesSize, int location) { Queue q; initQueue(q); Task task; int time 0; // 初始化队列 for (int i 0; i prioritiesSize; i) { task.priority priorities[i]; task.originalIndex i; enqueue(q, task); } while (!isEmpty(q)) { dequeue(q, task); // 当前任务出队 bool hasHigher false; int len queueLength(q); // 遍历当前队列中的剩余任务检查是否有更高优先级 for (int i 0; i len; i) { Task temp getElement(q, i); if (temp.priority task.priority) { hasHigher true; break; } } if (hasHigher) { // 重新入队 enqueue(q, task); } else { // 打印 time; if (task.originalIndex location) { return time; } // 否则task已被移除打印继续循环 } } return -1; // 不应执行到此 }C语言实现要点分析手动实现队列这是C语言版本的核心。我们实现了循环队列来避免假溢出。front和rear指针的移动都需要取模% MAX_SIZE。结构体封装使用Task结构体将优先级和索引绑定与面向对象语言中的类作用类似使数据管理更清晰。队列遍历由于是我们自己实现的队列遍历需要小心。queueLength函数计算当前队列长度getElement函数根据相对位置从队头开始的偏移量获取元素。注意计算实际数组下标时的取模操作。指针传递队列操作函数都接受队列指针Queue *q避免拷贝整个结构体。出队函数通过Task *task参数返回出队的元素。健壮性入队和出队函数都有返回值bool可以处理队列满或空的情况。在本题设定下任务数通常不会超过MAX_SIZE但这是一个好习惯。3.5 JavaScript (Node.js) 实现JavaScript的数组非常灵活可以轻松模拟队列操作。在算法题环境中我们通常用数组来实现。function printOrder(priorities, location) { // 创建队列每个元素是[优先级, 初始索引]的数组 let queue []; for (let i 0; i priorities.length; i) { queue.push([priorities[i], i]); } let time 0; while (queue.length 0) { // 取出队首元素 let current queue.shift(); // current [priority, index] let currentPriority current[0]; let currentIndex current[1]; // 检查剩余队列中是否有更高优先级的任务 // 使用数组的some方法非常符合语义 let hasHigherPriority queue.some(task task[0] currentPriority); if (hasHigherPriority) { // 重新放入队尾 queue.push(current); } else { // 打印当前任务 time; if (currentIndex location) { return time; } // 否则当前任务被移除继续循环 } } // 理论上不会执行到这里 return time; } // 示例用法 // let priorities [2, 1, 3, 2]; // let location 2; // console.log(printOrder(priorities, location)); // 输出1JavaScript实现要点分析使用数组模拟队列JavaScript数组的shift()方法移除并返回第一个元素队头push()方法在末尾添加元素队尾完美模拟了队列的FIFO操作。虽然shift()在V8引擎中对大型数组可能不是严格的O(1)但对于机试题的数据规模完全足够且代码极其简洁。Array.some()方法这是判断“是否存在”某元素的理想方法。queue.some(task task[0] currentPriority)会遍历queue直到找到一个优先级更高的任务就返回true否则返回false。它比先用filter生成新数组再判断长度要高效得多。元素表示用二元数组[priority, index]表示一个任务简单直接。通过current[0]和current[1]访问或者像代码中那样解构赋值到有意义的变量名。严格相等判断索引时使用严格相等这是一个好习惯。函数式风格结合箭头函数和some方法代码具有很好的可读性体现了现代JavaScript的风格。4. 解题思路的深度扩展与变体探讨掌握了基础解法我们可以进一步思考这道题还能怎么变如何举一反三4.1 性能优化从O(n^2)到O(n log n)我们之前的解法在检查“是否有更高优先级”时最坏情况下每次都需要遍历整个队列长度为k而总共可能需要操作n个任务因此最坏时间复杂度是O(n^2)。当任务数量很大时比如10^5这可能成为瓶颈。优化思路我们之所以要遍历是为了知道当前队列中的最高优先级是多少。如果我们能随时快速获取当前队列中的最高优先级就能将判断操作从O(n)降到O(1)。如何快速获取答案是使用一个辅助数据结构来维护优先级信息。一个非常合适的选择是最大堆Max Heap或者一个排序的计数器。方案一使用最大堆优先队列初始化时除了任务队列queue再建立一个最大堆maxHeap存储所有任务的优先级。每次从queue取出队头任务current时也从maxHeap取出堆顶元素maxP即当前全局最高优先级。比较current.priority和maxP如果current.priority maxP说明有更高优先级任务将current重新入队queue并且把maxP重新放回堆中因为maxP对应的任务还在队列里。如果current.priority maxP说明current就是最高优先级之一可以打印。弹出maxHeap的堆顶因为该优先级的一个任务被消耗了然后处理current。如何知道maxP对应的任务是否还在队列中我们不需要知道具体是哪个任务只需要知道这个优先级的任务是否还有。我们可以用一个哈希表或数组priorityCount来记录每个优先级剩余的任务数量。每次打印一个优先级为p的任务就将priorityCount[p]减1。当priorityCount[p]减到0时我们需要从maxHeap中移除所有优先级为p的元素实际上在堆中我们只关心存在的优先级可以用“惰性删除”技巧每次从堆顶取元素时检查其计数是否大于0如果不大于0则直接弹出并继续取下一个堆顶。这个方案将时间复杂度优化到了O(n log n)因为每个任务入队出队是O(1)但堆操作是O(log n)。空间复杂度是O(n)。方案二使用排序的优先级列表由于优先级范围通常很小如1-9我们可以用一个长度为10的数组count来记录每个优先级剩余的任务数。同时我们维护一个变量maxPriority表示当前队列中的最高优先级。初始化时遍历priorities填充count数组并找到初始的maxPriority。处理队头任务current如果current.priority maxPriority重新入队。如果current.priority maxPriority打印它。然后count[current.priority]--。如果count[maxPriority]变为0则需要将maxPriority递减直到找到下一个count[p] 0的优先级p。 这个方案的时间复杂度是O(n * k)其中k是优先级范围最大为9因此几乎是O(n)的空间复杂度O(1)如果优先级范围固定。这是针对本题特点优先级范围小的最优解法。4.2 变体问题如果优先级相同按输入顺序打印原题通常隐含了这个条件。我们的解法已经天然满足因为队列是FIFO的当优先级相同时先入队的任务会先被检查、先被打印。4.3 变体问题求所有任务的打印顺序如果题目不是求某个特定任务的顺序而是要求输出一个数组表示每个任务按其初始位置被打印的顺序。我们只需要稍微修改一下算法在任务被打印时time之后将time值记录到结果数组的对应初始索引位置上即可。4.4 变体问题多打印机协同这是一个更复杂的扩展。假设有M台相同的打印机它们共享同一个任务队列。每次每台打印机都试图从队列头取任务规则相同如果队头不是最高优先级则移动队尾。这就需要模拟多个并行的“打印头”。解决方案可以是使用多个队列或者仍然使用一个队列但用一个状态数组标记每个任务的状态等待、正在打印、完成。这更接近于操作系统中多核CPU的调度问题。5. 机试实战技巧与避坑指南基于这道题我总结了一些华为OD机试以及其他公司机试的通用技巧和常见“坑点”。5.1 输入输出处理IO这是机试的第一道关卡很多同学思路正确却栽在IO上。C使用cin和cout。注意如果输入输出量巨大可以在main函数开头加上ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);来关闭C和C流的同步加快速度。结尾输出不要忘记换行。Java使用Scanner进行输入对于大量数据可以使用BufferedReader和InputStreamReader组合效率更高。输出用System.out.println。Python使用input()读取一行。如果有多组测试用例常用while True: try: line input() except EOFError: break。输出用print()。C使用scanf和printf。注意scanf读取字符串时的缓冲区问题。JavaScript (Node.js)通常使用readline模块逐行读取。需要熟悉这种异步IO的写法。避坑务必根据题目给出的输入样例格式来编写你的IO代码。是单组数据还是多组数字是用空格隔开还是换行输出是每个结果一行还是空格隔开仔细审题。5.2 边界条件与特殊测试用例对于“打印任务排序”要主动思考并测试以下情况只有一个任务priorities [5], location 0。应该返回1。你的代码循环能正常处理吗目标任务是最高优先级且在队头priorities [9, 1, 1], location 0。应该返回1。检查你的“是否有更高优先级”逻辑在队列只剩队头一个元素时遍历是否会出错目标任务是最低优先级且在队尾priorities [9, 8, 7, 1], location 3。需要等其他所有任务都打完才轮到自己。模拟一下过程。所有任务优先级相同priorities [2, 2, 2], location 1。此时应该严格按照FIFO顺序打印你的代码中any(p current_priority)的判断结果永远是false所以顺序就是初始顺序。对于location1应该返回2。空输入虽然题目一般不会给但考虑一下你的函数是否能处理priorities为空数组的情况通常应该返回0或根据题意处理。在写完代码后不要只用题目给的例子自己构造这些边界用例在脑子里或纸上跑一遍。5.3 调试与打印日志机试环境通常不允许使用IDE的调试器。最可靠的调试方法是打印中间变量。在模拟循环的关键步骤打印出当前时间、队头任务信息、队列状态等。例如在else分支打印任务里加一句打印printf(Time %d: Print task idx%d, prio%d\n, time1, current.index, current.prio);注意先1再打印因为time是打印后才加。对比你的手动模拟结果和程序输出能快速定位逻辑错误。5.4 时间与空间复杂度分析即使机试不明确要求写分析在思考时也要有这个概念。对于本题基础解法时间复杂度O(n^2)空间复杂度O(n)用于存储队列。优化解法计数数组时间复杂度O(n * k)k为优先级范围可视为O(n)空间复杂度O(k)。 在面试中面试官可能会追问“如果任务数量n很大10^6优先级范围也很大1-10^5你的算法会怎么样如何优化” 这时你就可以引出最大堆计数器的优化思路。5.5 代码风格与可读性清晰的代码能让你在检查时更快地发现错误也可能让阅卷人如果是人工判卷留下好印象。命名变量名用taskQueue,currentTask,targetIndex而不是q,t,loc。注释在关键步骤比如判断是否有更高优先级、找到目标后返回的地方写简要注释。函数拆分如果语言允许如Java、Python可以把“检查是否有更高优先级”这个逻辑抽成一个单独的函数如bool hasHigherPriority(queue, currentPrio)使主循环更清晰。避免魔法数字比如优先级范围可以定义成常量const int MAX_PRIORITY 9;。6. 从考题到实际应用优先级队列的应用场景最后我们来聊聊这道题背后的实际价值。它不仅仅是一道考题其核心模型——优先级队列Priority Queue——在软件开发中无处不在。操作系统进程/线程调度这是最直接的应用。操作系统中的调度器就维护着一个就绪队列进程/线程带有优先级如实时进程、普通进程。调度器总是选择优先级最高的进程投入运行。如果高优先级进程不断到来低优先级进程就可能被“饿死”这对应了本题中低优先级任务可能永远无法打印的情况现实中操作系统会有老化机制来避免饿死。网络数据包调度在网络路由器中不同服务类型如语音、视频、普通数据的数据包有不同的优先级和延迟要求。路由器使用优先级队列来确保高优先级的包先被转发。消息队列中间件如RabbitMQ、Kafka通过某些插件或策略支持优先级队列。生产者在发送消息时可以指定优先级消费者会优先消费高优先级的消息。这在电商订单处理VIP订单优先、客服系统紧急工单优先中非常有用。定时任务系统很多系统需要执行定时任务Cron Job。如果任务执行时间有重叠系统可能需要根据任务的紧急程度优先级来决定执行顺序。游戏开发在游戏服务器中玩家的操作请求如移动、释放技能可能会被放入一个队列。为了保证游戏体验某些关键技能如治疗、控制可能需要更高的优先级来快速响应。事件驱动架构在一个事件总线上不同的事件处理器可能会监听多种事件。系统可能需要根据事件类型的重要性优先级来决定处理的顺序。当你理解了“打印任务排序”这道题你就掌握了优先级队列最基本的工作机制。在实际项目中你可能会直接使用语言内置的优先队列库如Java的PriorityQueuePython的heapq但底层的思想是相通的如何高效地管理一组元素并能快速访问或移除其中“最重要”的那一个。所以下次当你看到这类题目时不妨多想一想这个模型能解决我工作中遇到的什么问题这样的思考会让你的学习从“应试”真正走向“应用”。