Medium
题目描述
给你 n 个任务,编号从 0 到 n - 1,由二维整数数组 tasks 表示,其中 tasks[i] = [enqueueTimei, processingTimei] 意味着第 i 个任务将在 enqueueTimei 时刻变为可用,并且需要 processingTimei 的时间来完成处理。
你有一个单线程 CPU,它一次最多只能处理一个任务,并将按以下方式运行:
- 如果 CPU 空闲且没有可处理的任务,则 CPU 保持空闲状态。
- 如果 CPU 空闲且有可用任务,CPU 将选择处理时间最短的任务。如果多个任务有相同的最短处理时间,它将选择索引最小的任务。
- 一旦任务开始,CPU 将在不停止的情况下处理整个任务。
- CPU 可以在完成任务后立即开始新任务。
返回 CPU 处理任务的顺序。
示例 1:
输入:tasks = [[1,2],[2,4],[3,2],[4,1]]
输出:[0,2,3,1]
解释:事件按以下顺序发生:
- 时间 = 1,任务 0 可以处理。可用任务 = {0}。
- 同时在时间 = 1,空闲的 CPU 开始处理任务 0。可用任务 = {}。
- 时间 = 2,任务 1 可以处理。可用任务 = {1}。
- 时间 = 3,任务 2 可以处理。可用任务 = {1, 2}。
- 同时在时间 = 3,CPU 完成任务 0 并开始处理任务 2,因为它是最短的。可用任务 = {1}。
- 时间 = 4,任务 3 可以处理。可用任务 = {1, 3}。
- 时间 = 5,CPU 完成任务 2 并开始处理任务 3,因为它是最短的。可用任务 = {1}。
- 时间 = 6,CPU 完成任务 3 并开始处理任务 1。可用任务 = {}。
- 时间 = 10,CPU 完成任务 1 并变为空闲。
示例 2:
输入:tasks = [[7,10],[7,12],[7,5],[7,4],[7,2]]
输出:[4,3,2,0,1]
提示:
tasks.length == n1 <= n <= 10^51 <= enqueueTimei, processingTimei <= 10^9
解题思路
这是一道模拟题,需要按照单线程 CPU 的执行规则来处理任务。
核心思路:
- 排序预处理:首先按照任务的入队时间对所有任务进行排序,这样可以按时间顺序处理可用任务。
- 优先队列管理可用任务:使用最小堆来存储当前可用的任务,堆的排序规则是:优先处理时间短的任务,处理时间相同时优先索引小的任务。
- 时间模拟:维护当前时间,当 CPU 完成一个任务后,需要将所有在当前时间之前入队的新任务加入优先队列。
算法流程:
- 将任务与其原始索引一起排序
- 初始化当前时间为 0,使用指针追踪下一个待入队的任务
- 当还有任务未处理时:
- 如果优先队列为空且当前时间小于下一个任务的入队时间,则跳转到该任务的入队时间
- 将所有在当前时间可用的任务加入优先队列
- 从优先队列中取出最优任务执行,更新当前时间
时间复杂度分析:
- 排序:O(n log n)
- 堆操作:每个任务最多入队出队一次,总共 O(n log n)
- 总体:O(n log n)
代码实现
class Solution {
public:
vector<int> getOrder(vector<vector<int>>& tasks) {
int n = tasks.size();
vector<pair<pair<int, int>, int>> sortedTasks; // {{enqueueTime, processingTime}, originalIndex}
for (int i = 0; i < n; i++) {
sortedTasks.push_back({{tasks[i][0], tasks[i][1]}, i});
}
sort(sortedTasks.begin(), sortedTasks.end());
// Min heap: {processingTime, originalIndex}
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> availableTasks;
vector<int> result;
long long currentTime = 0;
int taskIndex = 0;
while (result.size() < n) {
// If no tasks available and current time is before next task's enqueue time
if (availableTasks.empty() && taskIndex < n && currentTime < sortedTasks[taskIndex].first.first) {
currentTime = sortedTasks[taskIndex].first.first;
}
// Add all tasks that are available at current time
while (taskIndex < n && sortedTasks[taskIndex].first.first <= currentTime) {
availableTasks.push({sortedTasks[taskIndex].first.second, sortedTasks[taskIndex].second});
taskIndex++;
}
// Process the task with shortest processing time (and smallest index if tie)
auto task = availableTasks.top();
availableTasks.pop();
result.push_back(task.second);
currentTime += task.first;
}
return result;
}
};
class Solution:
def getOrder(self, tasks: List[List[int]]) -> List[int]:
import heapq
n = len(tasks)
# Create list of (enqueueTime, processingTime, originalIndex)
sorted_tasks = [(tasks[i][0], tasks[i][1], i) for i in range(n)]
sorted_tasks.sort()
available_tasks = [] # Min heap: (processingTime, originalIndex)
result = []
current_time = 0
task_index = 0
while len(result) < n:
# If no tasks available and current time is before next task's enqueue time
if not available_tasks and task_index < n and current_time < sorted_tasks[task_index][0]:
current_time = sorted_tasks[task_index][0]
# Add all tasks that are available at current time
while task_index < n and sorted_tasks[task_index][0] <= current_time:
heapq.heappush(available_tasks, (sorted_tasks[task_index][1], sorted_tasks[task_index][2]))
task_index += 1
# Process the task with shortest processing time
processing_time, original_index = heapq.heappop(available_tasks)
result.append(original_index)
current_time += processing_time
return result
public class Solution {
public int[] GetOrder(int[][] tasks) {
int n = tasks.Length;
var sortedTasks = new List<(long enqueueTime, int processingTime, int originalIndex)>();
for (int i = 0; i < n; i++) {
sortedTasks.Add((tasks[i][0], tasks[i][1], i));
}
sortedTasks.Sort((a, b) => a.enqueueTime.CompareTo(b.enqueueTime));
var availableTasks = new PriorityQueue<(int processingTime, int originalIndex), (int, int)>(
Comparer<(int, int)>.Create((a, b) =>
a.Item1 != b.Item1 ? a.Item1.CompareTo(b.Item1) : a.Item2.CompareTo(b.Item2))
);
var result = new List<int>();
long currentTime = 0;
int taskIndex = 0;
while (result.Count < n) {
if (availableTasks.Count == 0 && taskIndex < n && currentTime < sortedTasks[taskIndex].enqueueTime) {
currentTime = sortedTasks[taskIndex].enqueueTime;
}
while (taskIndex < n && sortedTasks[taskIndex].enqueueTime <= currentTime) {
availableTasks.Enqueue(
(sortedTasks[taskIndex].processingTime, sortedTasks[taskIndex].originalIndex),
(sortedTasks[taskIndex].processingTime, sortedTasks[taskIndex].originalIndex)
);
taskIndex++;
}
var task = availableTasks.Dequeue();
result.Add(task.originalIndex);
currentTime += task.processingTime;
}
return result.ToArray();
}
}
var getOrder = function(tasks) {
const n = tasks.length;
const indexedTasks = tasks.map((task, i) => [task[0], task[1], i]);
indexedTasks.sort((a, b) => a[0] - b[0]);
const result = [];
const heap = [];
let time = 0;
let i = 0;
const heapPush = (item) => {
heap.push(item);
let idx = heap.length - 1;
while (idx > 0) {
const parent = Math.floor((idx - 1) / 2);
if (heap[parent][0] > heap[idx][0] ||
(heap[parent][0] === heap[idx][0] && heap[parent][1] > heap[idx][1])) {
[heap[parent], heap[idx]] = [heap[idx], heap[parent]];
idx = parent;
} else {
break;
}
}
};
const heapPop = () => {
if (heap.length === 0) return null;
if (heap.length === 1) return heap.pop();
const result = heap[0];
heap[0] = heap.pop();
let idx = 0;
while (true) {
const left = 2 * idx + 1;
const right = 2 * idx + 2;
let smallest = idx;
if (left < heap.length &&
(heap[left][0] < heap[smallest][0] ||
(heap[left][0] === heap[smallest][0] && heap[left][1] < heap[smallest][1]))) {
smallest = left;
}
if (right < heap.length &&
(heap[right][0] < heap[smallest][0] ||
(heap[right][0] === heap[smallest][0] && heap[right][1] < heap[smallest][1]))) {
smallest = right;
}
if (smallest === idx) break;
[heap[idx], heap[smallest]] = [heap[smallest], heap[idx]];
idx = smallest;
}
return result;
};
while (result.length < n) {
while (i < n && indexedTasks[i][0] <= time) {
heapPush([indexedTasks[i][1], indexedTasks[i][2]]);
i++;
}
if (heap.length === 0) {
time = indexedTasks[i][0];
} else {
const [processingTime, taskIndex] = heapPop();
result.push(taskIndex);
time += processingTime;
}
}
return result;
};
复杂度分析
| 复杂度类型 | 值 |
|---|---|
| 时间复杂度 | O(n log n) |
| 空间复杂度 | O(n) |
时间复杂度: O(n log n),其中 n 是任务数量。主要来自于初始排序和优先队列操作。
空间复杂度: O(n),用于存储排序后的任务列表、优先队列和结果数组。