当前位置: 首页 > news >正文

贪心算法与优先队列:华为OD流水线调度问题的多语言实现详解

1. 项目概述与核心价值

最近在技术社区和求职圈里,“华为OD机试”的热度一直居高不下,尤其是其中的算法题,成了很多朋友准备面试的“必修课”。我注意到,有一类题目出现的频率相当高,那就是流水线调度问题。这不仅仅是一道单纯的编程题,它背后考察的是对贪心算法、优先队列(堆)以及多任务并行处理思想的深刻理解。无论是准备华为OD,还是想夯实自己的算法基础,吃透这道题都大有裨益。

今天,我们就来彻底拆解这道“流水线调度”算法题。我会从最朴素的思路讲起,一步步推导到最优解,并给出C++、Java、JavaScript 和 Python四种主流语言的完整实现。更重要的是,我会分享在实现过程中容易踩的“坑”,以及如何根据不同的语言特性,写出既高效又优雅的代码。无论你是算法新手,还是想看看不同语言实现差异的老手,这篇文章都能给你带来实实在在的收获。

2. 问题深度解析与建模思路

2.1 题目还原与关键点提炼

我们先抛开代码,把问题本身吃透。题目通常这样描述:

一个工厂有m条流水线,需要并行完成n个独立的作业。每个作业的处理时间为t[i]。调度规则是:总是优先将处理时间最短的作业安排到空闲的流水线上。 具体流程:

  1. n > m时,先选取处理时间最短的m个作业,分别放入m条流水线开始处理。
  2. 每当有任意一个作业完成时(即某条流水线空闲),就从剩余未调度的作业中,再选取处理时间最短的一个,放入这条空闲流水线。
  3. 重复步骤2,直到所有作业都被调度完毕。
  4. 求处理完所有作业的总耗时(即从开始到所有流水线都空闲下来的时刻)。

核心约束与规则解读:

  • 作业独立:作业之间没有依赖关系,可以任意调度。
  • 非抢占式:一个作业一旦开始在某条流水线上处理,就必须一直占用该流水线直到完成,中间不能被中断或切换。
  • 贪心策略:每次调度都选择“当前剩余作业中处理时间最短的”。这是解决问题的关键,它保证了整体完成时间尽可能早。
  • 目标:不是所有作业处理时间的总和,而是整个调度过程的最大完成时间(Makespan)

这本质上是一个经典的多机调度问题(Parallel Machine Scheduling)的简化版,并且采用了“最短处理时间优先(SPT)”的贪心规则。理解了这个模型,我们就知道,解题的核心在于模拟这个动态的调度过程。

2.2 算法思路设计与选型

如何用程序来模拟这个调度过程呢?最直观的想法可能是用时间轴一步步推进,但那样效率太低。我们可以转换一下视角:

  1. 初始化阶段:我们有m条流水线,可以看作m个初始完成时间为0的“处理器”。我们有n个作业,每个作业有处理时间。
  2. 核心操作:总是找到当前最早空闲的流水线(即其当前负载完成时间最小),然后将当前剩余作业中处理时间最短的分配给它。这条流水线新的完成时间 = 原完成时间 + 新作业的处理时间。
  3. 重复:直到所有作业都被分配完毕。
  4. 结果:所有流水线完成时间中的最大值,就是总耗时。

看到这里,有两个“总是找最值”的操作:

  • 找当前最早空闲的流水线(找最小值)。
  • 找剩余作业中最短处理时间的作业(找最小值)。

这强烈提示我们需要使用一种能高效获取并移除最小值、以及插入新值的数据结构——最小堆(Min-Heap)

因此,算法步骤可以精炼为:

  1. 对作业处理时间数组t进行升序排序。这样我们就能按顺序获取“当前最短的作业”。
  2. 初始化一个最小堆heap,用来表示每条流水线当前的累计完成时间。初始时,堆中有m个0(代表m条空闲的流水线)。
  3. 遍历排序后的作业列表: a. 从堆中弹出最小值(即当前最早空闲的流水线时间current_time)。 b. 将当前作业分配给该流水线,则该流水线新的完成时间为current_time + t[i]。 c. 将这个新的完成时间压入堆中。
  4. 遍历完所有作业后,堆中剩下的就是各流水线最终的完成时间。堆中的最大值(即所有完成时间中的最大值)就是答案。
  5. 有一个特殊情况:如果作业数n小于等于流水线数m,那么所有作业可以立即并行开始。总耗时就是最长作业的处理时间,即max(t)。我们的堆模拟法同样能处理这种情况。

这个算法的时间复杂度是 O(n log m),其中排序是 O(n log n),但堆操作是 O(n log m),通常 m 远小于 n,所以效率很高。空间复杂度是 O(m) 用于维护堆。

注意:这里容易产生一个误区,认为需要两个堆(一个存流水线时间,一个存作业时间)。实际上,因为作业是预先排序好的,我们只需要按顺序取即可,所以一个存流水线时间的堆就足够了。这是优化思路的关键。

3. 核心数据结构:优先队列(堆)的实现差异

算法骨架清晰了,但在不同语言中,如何实现这个“最小堆”是第一个需要跨越的坎。四种语言对优先队列的支持各不相同,这也是多语言实现时最有趣的部分。

3.1 C++:std::priority_queue的“反向”技巧

C++的标准库std::priority_queue默认是一个最大堆(队首元素最大)。我们需要的是最小堆。有两种经典方法:

方法一:存入负数,取出时再取反。这是最直接的方法。我们将流水线的完成时间以负数形式存入,那么绝对值最小的负数(即原本的最大值)会在堆顶,但因为我们存的是负值,所以堆顶的负数对应的原正数就是最小值。取出时,再取负得到原值。

#include <queue> #include <vector> #include <algorithm> using namespace std; // 使用默认最大堆,通过存入负数实现最小堆逻辑 priority_queue<long long> maxHeap; // 实际上我们把它当最小堆用 long long finishTime = 5; maxHeap.push(-finishTime); // 存入 -5 // 当需要获取最早完成时间时 long long earliest = -maxHeap.top(); // 取出 -5,取反得 5 maxHeap.pop();

方法二:使用自定义比较器。std::priority_queue的模板有三个参数:元素类型、底层容器类型、比较器。我们可以定义一个greater比较器来直接实现最小堆。

#include <queue> #include <vector> #include <algorithm> #include <functional> // 需要引入 functional 以使用 greater using namespace std; // 使用 greater 比较器,使得优先级小的(即数值小的)在堆顶 priority_queue<long long, vector<long long>, greater<long long>> minHeap; minHeap.push(5); long long earliest = minHeap.top(); // 直接得到 5

在华为OD机试的环境下,两种方法都可以。我个人更推荐第二种,因为它的意图更清晰,代码可读性更好,不容易在“取反”逻辑上出错。虽然需要多写一点模板参数,但绝对是值得的。

3.2 Java:灵活的PriorityQueue

Java的java.util.PriorityQueue默认是最小堆。这正好符合我们的需求,非常方便。

import java.util.PriorityQueue; PriorityQueue<Long> minHeap = new PriorityQueue<>(); minHeap.add(5L); long earliest = minHeap.poll(); // 得到 5

如果需要最大堆,则在构造时传入一个Comparator.reverseOrder()

PriorityQueue<Long> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());

Java的这种设计让它在实现这类算法时非常简洁。

3.3 Python:heapq模块的原地操作

Python的标准库heapq提供的是基于列表的堆算法,它只提供最小堆操作。它不是一个类,而是一组函数(heapify,heappush,heappop等),直接操作一个列表,将这个列表在原地维护成堆结构。

import heapq heap = [] # 一个普通列表 heapq.heappush(heap, 5) # 列表被当作堆来操作 earliest = heapq.heappop(heap) # 得到 5

这里有一个非常重要的坑:heapq不提供“查看堆顶元素而不弹出”的单独函数。heap[0]就是堆顶元素。但在弹出前,你必须确保列表不为空。

if heap: # 必须判断,否则 heap[0] 可能索引错误 top = heap[0]

Python的实现非常轻量,但需要习惯这种函数式操作列表的风格。

3.4 JavaScript:手动实现或使用数组排序

在标准的ES5/ES6环境中,没有内置的优先队列数据结构。常见的做法有几种:

  1. 数组模拟:每次需要最小值时,对数组进行排序(sort),然后取第一个元素。这种方法在数据量小(如机试题规模)时完全可行,且代码简单。

    let heap = [5, 3, 8]; heap.sort((a, b) => a - b); // 升序排序 let earliest = heap.shift(); // 取出并移除第一个元素(最小值)

    缺点是每次操作都是 O(n log n),但题目约束通常允许。

  2. 手动实现最小堆:实现一个MinHeap类,包含insert,extractMin,peek等方法。这能保证 O(log n) 的操作效率,是更专业的做法,但机试时间紧张时,实现起来有风险。

  3. 使用第三方库或环境特定API:在Node.js或某些环境中,可能有类似库,但华为OD机试环境通常是最纯净的标准JavaScript环境,不保证有外部库。

对于华为OD机试,我强烈建议使用第一种“数组模拟排序”的方法。理由如下:

  • 题目输入的nm通常不会太大(比如n <= 10000)。
  • 代码极其简单,不易出错,能把时间花在核心逻辑上。
  • 在有限的机试时间内,正确性可完成性远高于极致的性能优化。

实操心得:在跨语言实现算法时,首先要摸清每种语言的核心工具库。C++要熟悉priority_queue的模板;Java的PriorityQueue是利器;Python的heapq要小心操作原列表;JavaScript则要懂得变通,用最稳妥的方式达到目的。不要试图在一种语言里用另一种语言的思维去写代码。

4. 多语言完整实现与逐行解析

掌握了核心思路和数据结构,现在我们用四种语言分别实现完整的算法。我会对关键代码行加上详细注释。

4.1 C++ 实现

#include <iostream> #include <vector> #include <algorithm> #include <queue> // 包含 priority_queue #include <functional> // 包含 greater using namespace std; long long calculateTime(int m, vector<int>& jobs) { int n = jobs.size(); // 特殊情况:作业数不超过流水线数,并行执行,时间为最长作业耗时 if (n <= m) { return *max_element(jobs.begin(), jobs.end()); } // 1. 对作业按处理时间升序排序(贪心:总是先处理短作业) sort(jobs.begin(), jobs.end()); // 2. 初始化最小堆,记录每条流水线当前的完成时间 // 使用 greater<long long> 使得数值小的优先级高(最小堆) priority_queue<long long, vector<long long>, greater<long long>> pq; // 3. 初始时,m条流水线都空闲,完成时间均为0 for (int i = 0; i < m; ++i) { pq.push(0); } // 4. 依次分配每一个作业 for (int time : jobs) { // 取出当前最早空闲的流水线(其完成时间最小) long long earliestFinish = pq.top(); pq.pop(); // 将当前作业分配给该流水线,更新其完成时间 long long newFinish = earliestFinish + time; // 将更新后的时间重新加入堆中 pq.push(newFinish); } // 5. 所有作业分配完毕,堆中存放的是各流水线最终的完成时间 // 我们需要找到其中的最大值,即总耗时 long long totalTime = 0; while (!pq.empty()) { // 因为是最小堆,直接 pop 出来的不是最大值,需要遍历比较 totalTime = max(totalTime, pq.top()); pq.pop(); } return totalTime; } int main() { // 示例输入:假设第一行是 m 和 n,第二行是 n 个作业时间 // 例如:3 5\n 5 3 6 1 2 int m, n; cin >> m >> n; vector<int> jobs(n); for (int i = 0; i < n; ++i) { cin >> jobs[i]; } long long result = calculateTime(m, jobs); cout << result << endl; return 0; }

C++实现关键点解析:

  1. 类型选择:使用long long存储完成时间,防止大数相加溢出。即使作业时间timeint,多次累加后也可能超出int范围。
  2. 堆的定义priority_queue<long long, vector<long long>, greater<long long>> pq是定义最小堆的标准写法。记住这个模板。
  3. 结果获取:分配结束后,堆里是各流水线的完成时间。由于是最小堆,top()是最小值,我们需要遍历整个堆来找到最大值。也可以用一个变量在每次push(newFinish)时更新最大值,这样最后就不需要遍历堆了,效率更高。
  4. 输入处理:华为OD机试通常是核心代码模式(只需实现函数)或ACM模式(需要自己处理输入输出)。上述main函数展示的是ACM模式。

4.2 Java 实现

import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int m = scanner.nextInt(); int n = scanner.nextInt(); int[] jobs = new int[n]; for (int i = 0; i < n; i++) { jobs[i] = scanner.nextInt(); } long result = calculateTime(m, jobs); System.out.println(result); scanner.close(); } public static long calculateTime(int m, int[] jobs) { int n = jobs.length; // 特殊情况处理 if (n <= m) { long max = 0; for (int time : jobs) { if (time > max) max = time; } return max; } // 1. 排序 Arrays.sort(jobs); // 2. 初始化最小优先队列(Java的PriorityQueue默认就是最小堆) PriorityQueue<Long> pq = new PriorityQueue<>(); // 3. 初始化m条空闲流水线 for (int i = 0; i < m; i++) { pq.offer(0L); // 注意使用0L,自动转换为Long类型 } // 4. 分配作业 for (int time : jobs) { long earliestFinish = pq.poll(); // 取出最早空闲的流水线时间 long newFinish = earliestFinish + time; // 分配新作业 pq.offer(newFinish); // 流水线重新进入调度队列 } // 5. 找出最终的最大完成时间 long totalTime = 0; // 这里不能直接遍历PriorityQueue,它的迭代器不保证顺序。我们依次弹出即可。 while (!pq.isEmpty()) { totalTime = Math.max(totalTime, pq.poll()); } return totalTime; } }

Java实现关键点解析:

  1. 自动装箱与类型pq.offer(0L)中的0Llong字面量,会被自动装箱为Long。如果写pq.offer(0),是int会被装箱为Integer,与PriorityQueue<Long>类型不匹配,编译会报错。
  2. 遍历堆PriorityQueue的迭代器 (iterator()) 不保证按优先级顺序遍历。因此,要获取所有元素,唯一可靠的方式是不断调用poll()直到队列为空。这正是我们获取最终最大值的方法。
  3. 简洁性:得益于PriorityQueue默认的最小堆特性,Java版本的代码看起来非常清晰和直观。

4.3 JavaScript 实现(数组模拟法)

// 使用数组排序模拟最小堆,这是华为OD机试中最稳妥的JS实现方式 function calculateTime(m, jobs) { const n = jobs.length; // 特殊情况处理 if (n <= m) { return Math.max(...jobs); } // 1. 作业按处理时间升序排序 jobs.sort((a, b) => a - b); // 2. 初始化一个数组,表示m条流水线的当前完成时间,初始都为0 // 我们把这个数组始终维护成升序,这样第一个元素就是最早空闲的流水线时间 let lines = new Array(m).fill(0); // 3. 遍历每一个作业 for (let i = 0; i < n; i++) { // 当前最短作业的处理时间 const jobTime = jobs[i]; // 从lines数组头部取出最早空闲的流水线时间(因为lines是升序的) // 这里直接取第一个元素,并更新它 const earliestFinish = lines[0]; const newFinish = earliestFinish + jobTime; // 将更新后的时间放回数组,并重新排序,以保持数组升序 lines[0] = newFinish; lines.sort((a, b) => a - b); // 每次分配后都排序 } // 4. 所有作业分配完后,lines数组中最后一个元素(最大值)就是总耗时 // 因为lines是升序排列的 return lines[lines.length - 1]; } // 以下是模拟输入输出的部分,机试中可能需要根据题目要求调整 function main() { // 假设输入格式为:第一行 "m n",第二行 "t1 t2 ... tn" // 例如: "3 5\n5 3 6 1 2" const input = `3 5 5 3 6 1 2`; const lines = input.trim().split('\n'); const firstLine = lines[0].split(' ').map(Number); const m = firstLine[0]; const n = firstLine[1]; const jobs = lines[1].split(' ').map(Number); const result = calculateTime(m, jobs); console.log(result); } // 执行 main();

JavaScript实现关键点解析:

  1. 算法选择:没有使用复杂的手写堆,而是用数组lines配合每次sort来模拟。在nm不大时(m通常较小),O(n * m log m)的复杂度是可以接受的,代码极其简单。
  2. 模拟堆操作
    • lines[0]始终是当前最早空闲的流水线时间(最小值)。
    • 取出lines[0]并加上作业时间后,将结果写回lines[0]
    • 然后立即对lines进行排序,使其恢复升序,以便下一次循环。
  3. 结果获取:由于lines始终保持升序,所以最后lines[lines.length - 1]就是最大值,即总耗时。
  4. 输入处理:华为OD的JavaScript环境(通常为Node.js)需要自己处理输入。常用readline模块逐行读取。上面的main函数是静态示例,实际机试中需要动态读取。

4.4 Python 实现

import sys import heapq def calculate_time(m, jobs): n = len(jobs) # 特殊情况处理 if n <= m: return max(jobs) # 1. 对作业排序 jobs.sort() # 2. 初始化最小堆,表示流水线完成时间 # 使用列表,并通过heapq函数操作 heap = [] for _ in range(m): heapq.heappush(heap, 0) # 3. 分配作业 for time in jobs: # 取出当前最早完成的时间 earliest_finish = heapq.heappop(heap) # 分配新作业,计算新的完成时间 new_finish = earliest_finish + time # 将新的时间加入堆中 heapq.heappush(heap, new_finish) # 4. 此时堆中元素是各流水线的最终完成时间 # 我们需要其中的最大值。堆不保证顺序,所以需要遍历找出最大值 total_time = 0 # 注意:不能直接遍历heap,因为堆的列表顺序不是排序顺序。 # 我们只能通过 heappop 依次取出,或者用 max(heap)。 # 由于我们只需要最大值,且后续不再需要堆结构,可以用 max 函数。 total_time = max(heap) return total_time def main(): # 处理输入,假设为ACM模式 data = sys.stdin.read().strip().split() if not data: return m = int(data[0]) n = int(data[1]) jobs = list(map(int, data[2:2+n])) result = calculate_time(m, jobs) print(result) if __name__ == "__main__": main()

Python实现关键点解析:

  1. heapq的使用:记住heapq是模块,不是类。它通过heappushheappop等函数操作一个普通列表,使其满足堆的性质。操作后,列表heap本身就是一个堆。
  2. 获取最大值:在所有作业分配完成后,堆heap列表中存放了最终的完成时间,但列表的物理顺序并不是排序顺序。要得到最大值,不能直接取heap[-1],因为那只是列表最后一个元素,不一定是最大的。安全的方法是使用max(heap),或者用heappop把所有元素弹出并记录最大值。前者更简洁。
  3. 输入读取sys.stdin.read().strip().split()是一次性读取所有输入,非常方便。这在华为OD的Python环境中是常见且有效的做法。
  4. 类型处理:Python的整数是任意精度的,不用担心溢出问题,这是相比C++/Java的一个便利之处。

5. 常见问题、调试技巧与性能优化

即使理解了算法,在实现和调试时还是会遇到各种问题。这里我总结了一些典型的“坑”和解决技巧。

5.1 典型错误与排查清单

问题现象可能原因解决方案
结果比预期小1. 忽略了n <= m的特殊情况,错误地走了堆逻辑。
2. 在获取最终总耗时totalTime时,错误地取了堆顶元素(最小值)而不是最大值。
1. 在函数开头显式处理n <= m的情况,直接返回max(jobs)
2. 确认最终结果是从堆中找出最大值。C++/Java需要遍历,Python用max(),JS中我们的lines数组已排序。
结果比预期大作业没有排序,或者排序顺序错了(降序)。贪心策略失效。检查sort操作,确保是按升序(从小到大)排序。C++/Java的默认排序是升序,Python的list.sort()也是升序,JS的sort()默认按字符串排序,必须传入(a,b)=>a-b
输出负数或非常大整数溢出。作业时间累加后超出了int类型的范围。在C++和Java中,将用于累加完成时间的变量(如earliestFinish,newFinish,totalTime)定义为long long(C++) 或long(Java)。
JavaScript代码超时使用了低效的“数组模拟+每次全排序”方法,且数据量n*m较大。考虑优化:
1. 使用二分查找插入来维护有序数组,将排序的 O(m log m) 降为 O(log m)。
2. 手写一个最小堆类。但在华为OD的约束下,通常无需优化到这一步,先检查逻辑是否正确。
Python代码报错TypeError在堆中混用了intfloat,或者heapq操作的对象不是列表。确保heap始终是一个列表,并且只使用heapq模块的函数操作它。确保存入堆的元素类型一致。
输入读取错误输入格式处理不对,比如多读了或少读了数据。在本地用题目给的样例输入进行测试。打印出读取后的m,n,jobs数组,确认和输入一致。华为OD常考ACM模式输入。

5.2 调试与测试技巧

  1. 构造小样例:不要一上来就用复杂数据。从最简单的情况开始验证。

    • m=1, n=1, jobs=[5]-> 结果应为5。
    • m=3, n=3, jobs=[1,2,3]-> 三条线并行,结果应为max(1,2,3)=3
    • m=2, n=4, jobs=[3,1,4,2]-> 手动模拟:排序后[1,2,3,4]。线1:0+1=1,线2:0+2=2;线1:1+3=4,线2:2+4=6。结果应为6。
  2. 打印中间状态:在循环中打印关键变量,这是最有效的调试手段。

    # 在Python分配作业的循环中加入打印 for idx, time in enumerate(jobs): earliest_finish = heapq.heappop(heap) new_finish = earliest_finish + time heapq.heappush(heap, new_finish) print(f"分配作业{idx}(耗时{time})到完成时间为{earliest_finish}的流水线,新完成时间={new_finish}。当前堆状态:{heap}")
  3. 边界条件测试

    • mn等于0或1的情况(虽然题目可能保证大于0,但自己测试要覆盖)。
    • 所有作业时间都相同的情况。
    • 作业时间差异巨大的情况。

5.3 性能优化进阶思考

虽然上述实现已能通过绝大多数测试用例,但了解优化思路有助于应对更极端的情况。

  1. C++/Java/Python的堆实现:已经是理论最优的 O(n log m) 复杂度,很难有本质提升。关键在于正确使用数据结构。

  2. JavaScript的优化:如果确实遇到大数据量超时,可以手写最小堆。

    class MinHeap { constructor() { this.heap = []; } insert(val) { /* 实现插入后上浮 */ } extractMin() { /* 实现取出堆顶并下沉 */ } peek() { return this.heap[0]; } }

    将数组模拟法的 O(n * m log m) 优化为 O(n log m)。但机试中,除非明确卡这个点,否则用数组模拟更省时间。

  3. 最终最大值获取优化:可以在每次push(newFinish)时,用一个变量globalMax记录当前遇到的最大值,这样最后就不需要遍历堆或数组找最大值了。

    long long totalTime = 0; for (int time : jobs) { long long earliestFinish = pq.top(); pq.pop(); long long newFinish = earliestFinish + time; totalTime = max(totalTime, newFinish); // 实时更新最大值 pq.push(newFinish); } // 循环结束,totalTime 就是答案

    这个优化可以节省最后遍历堆的时间,代码也更简洁。强烈推荐在所有实现中加入这个优化。

6. 从解题到举一反三:算法思想的延伸

这道流水线调度题完美体现了贪心算法优先队列的应用。掌握它,你就能解决一大类“调度”和“资源分配”问题。

可以尝试用同样的思路解决以下问题:

  1. 会议室 II (LeetCode 253):给你若干会议的时间间隔,问至少需要多少间会议室。这相当于把会议(作业)分配到会议室(流水线),只不过作业有固定的开始和结束时间。核心是按开始时间排序,用最小堆维护当前正在使用的会议室的结束时间
  2. 任务调度器 (LeetCode 621):给定任务列表和冷却时间,求最短执行时间。虽然更复杂,但其中也需要用到类似“优先安排次数最多任务”的贪心思想。
  3. 多机调度问题变种:如果作业有优先级,或者流水线速度不同,又该如何建模?核心思路依然是贪心+优先队列,但比较的“键值”需要改变。

我个人在实现这类题目时的体会是:

  • 建模是关键:花时间把问题描述转化成清晰的数据模型(流水线=堆,作业时间=排序列表),比直接写代码更重要。
  • 语言工具要熟:知道每种语言里“优先队列”怎么用,能节省大量调试时间。C++的greater,Java的默认最小堆,Python的heapq,JS的数组模拟,这些都是必须刻在脑子里的。
  • 边界和溢出是常客n <= m的特殊情况、int溢出,这两个错误我几乎在每次第一次写这道题时都会犯。现在养成了习惯:看到累加先想long long,看到比较先想边界。
  • 测试要全面:自己构造的简单样例能快速验证逻辑主干。华为OD的测试用例往往会包含各种边界情况。

最后,再分享一个小技巧:在华为OD机试中,如果题目明确说了“流水线”和“最短作业优先”,那么基本可以确定就是这道题。直接套用这个“排序+最小堆”的模板,仔细处理好输入输出和边界条件,就能稳稳拿分。把这个模板练到肌肉记忆,你就掌握了攻克这一类题目的利器。

http://www.jsqmd.com/news/1204531/

相关文章:

  • 数据质量怎么提升才有效?
  • C++中nullptr与void*的本质区别及实战应用指南
  • Python科学计算第五章 字符串的使用
  • 中国人形机器人竞争力:工程化落地能力与核心瓶颈分析
  • Mac高效使用指南:从工具推荐到系统优化
  • CH32V307移植FATFS文件系统实战指南
  • 鸿蒙ArkUI实战:实现账单关键词搜索
  • 语言学习资源矩阵:从零基础到精通的实战指南
  • 计算机毕业设计之jsp校园网教室查询预约系统设计与实现
  • JetBrains MCP协议详解:AI IDE底层交互范式革命
  • YOLO26无NMS目标检测:架构革新与部署优化
  • 人形机器人:不是仿生炫技,而是适配人类世界的物理接口
  • GX Works2安装与FX系列PLC编程实战指南
  • 5分钟解锁网易云音乐加密文件:ncmdump完整免费解密指南
  • C++定时器实现:从最小堆与时间轮原理到多线程安全设计
  • 鸿蒙 ArkTS 实战:Music Practice Timer 从创作记录到状态反馈完整解析
  • 基于微信小程序的旧物交易平台系统开题报告
  • 2026年人形机器人产业落地实况:从Demo到产线的硬核跨越
  • 文件系统接口:存储管理的核心机制与应用实践
  • Windows软件故障排查与优化实战指南
  • 字节、阿里、腾讯为什么越来越爱开源?算完账就懂了。
  • Qt4 GUI编程实战:信号槽机制与现代开发环境适配指南
  • WAM:从VLA到世界动作建模的范式跃迁
  • 宇树科技与云深处IPO进展解析
  • Linux防火墙核心工具对比:iptables与firewalld原理、配置与选型指南
  • C++大学生技能展示平台:从零构建实战项目,打造高质量简历作品集
  • C++数据类型深度解析:从内存布局到智能指针的编程基石
  • C++多线程编程:深入理解std::mutex与std::lock_guard的RAII锁管理
  • 做B站别只盯播放量,B站关键词排名优化才是长尾流量入口
  • Linux chage命令详解:用户密码时效管理实战指南