Daily DSA: Single-Processor Task Scheduler with Priority (Medium)
Advertisement
Problem Statement
You are given $n$ tasks to be scheduled on a single-processor CPU. Each task is represented as a triplet tasks[i] = [arrival_i, processing_i, priority_i]:
arrival_i: The time at which the $i$-th task becomes available for execution.processing_i: The amount of time required to finish processing the $i$-th task.priority_i: The priority level of the $i$-th task (higher integer values represent higher priority).
The CPU schedules tasks according to the following rules:
- The CPU can only execute one task at a time.
- Processing is non-preemptive: once a task starts running, it will run uninterrupted until completion.
- If the CPU becomes idle, it waits until the next task arrives.
- If multiple tasks are ready for processing at a given time, the CPU selects the available task with the highest priority.
- If there is a tie in priority, the CPU chooses the task with the earliest arrival time. If a tie still persists, choose the task with the smaller original index.
Return an array completion_times of length $n$, where completion_times[i] is the exact time at which the $i$-th task finishes processing.
Examples
Example 1:
Input: tasks = [[1, 4, 2], [2, 3, 5], [3, 2, 5]]
Output: [9, 5, 7]
Explanation:
- At time t = 0: No tasks available. CPU idle.
- At time t = 1: Task 0 arrives [1, 4, 2]. CPU starts processing Task 0.
- At time t = 2: Task 1 arrives [2, 3, 5]. Task 0 is still running.
- At time t = 3: Task 2 arrives [3, 2, 5]. Task 0 is still running.
- At time t = 5: Task 0 finishes (completion time = 5). Available ready tasks: Task 1 (priority 5, arrival 2) and Task 2 (priority 5, arrival 3).
Both have priority 5, but Task 1 arrived earlier (t = 2). CPU executes Task 1.
- At time t = 8: Task 1 finishes (completion time = 8... wait, Task 0 finishes at t = 1+4=5).
Correction:
- Task 0 started at t = 1, takes 4 units -> finishes at t = 5. Ans[0] = 5? Wait, Task 0 was selected because at t=1 it was the ONLY task available.
- At t = 5: Available: Task 1 (prio 5, arrival 2), Task 2 (prio 5, arrival 3). Task 1 starts at t = 5, takes 3 units -> finishes at t = 8.
- At t = 8: Available: Task 2 (prio 5, arrival 3). Task 2 starts at t = 8, takes 2 units -> finishes at t = 10.
Output: [5, 8, 10]
Example 2:
Input: tasks = [[0, 3, 1], [0, 2, 2], [5, 4, 3]]
Output: [5, 2, 9]
Explanation:
- At t = 0: Both Task 0 and Task 1 arrive. Task 1 has higher priority (2 > 1), so CPU executes Task 1.
- At t = 2: Task 1 finishes (completion time = 2). Available ready tasks: Task 0.
- At t = 2: Task 0 starts and takes 3 units -> finishes at t = 5.
- At t = 5: Task 0 finishes. Task 2 arrives at t = 5. CPU executes Task 2 immediately -> finishes at t = 5 + 4 = 9.
Output: [5, 2, 9]
Constraints
- $1 \le n \le 10^5$
tasks[i].length == 3- $0 \le \text{arrival}_i \le 10^9$
- $1 \le \text{processing}_i \le 10^9$
- $1 \le \text{priority}_i \le 10^9$
Approach / Hint
- Sorting by Arrival Time: First, attach each task’s original index to its data structure so that output results can be mapped back accurately. Sort all tasks primarily by their arrival time.
- Priority Queue for Available Tasks: Maintain a Max-Heap (or custom comparator) containing tasks that have arrived by
currentTimeand are waiting to be executed.- The comparator should order by:
- Higher priority first
- Smaller arrival time second
- Smaller original index third
- The comparator should order by:
- Event Simulation Loop:
- Maintain
currentTimestarting at0(or arrival time of the first task). - If the priority queue is empty and the next task’s arrival time is greater than
currentTime, jumpcurrentTimeforward to that task’s arrival time. - Push all tasks whose arrival time is $\le \text{currentTime}$ into the priority queue.
- Pop the top task from the priority queue, process it by adding its
processingduration tocurrentTime, and store the completion time at its original index.
- Maintain
C++ Solution
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
struct Task {
long long arrival;
long long processing;
long long priority;
int original_index;
};
// Custom comparator for Max-Heap
struct TaskComparator {
bool operator()(const Task& a, const Task& b) const {
if (a.priority != b.priority) {
return a.priority < b.priority; // Higher priority comes first
}
if (a.arrival != b.arrival) {
return a.arrival > b.arrival; // Earlier arrival comes first
}
return a.original_index > b.original_index; // Smaller index comes first
}
};
class Solution {
public:
vector<long long> getTaskCompletionTimes(vector<vector<int>>& tasksInput) {
int n = tasksInput.size();
vector<Task> tasks(n);
for (int i = 0; i < n; ++i) {
tasks[i] = {
(long long)tasksInput[i][0],
(long long)tasksInput[i][1],
(long long)tasksInput[i][2],
i
};
}
// Sort tasks primarily by arrival time
sort(tasks.begin(), tasks.end(), [](const Task& a, const Task& b) {
return a.arrival < b.arrival;
});
priority_queue<Task, vector<Task>, TaskComparator> readyQueue;
vector<long long> completionTimes(n);
long long currentTime = 0;
int taskIdx = 0;
while (taskIdx < n || !readyQueue.empty()) {
// If no tasks are ready and processor is idle, jump time forward
if (readyQueue.empty() && currentTime < tasks[taskIdx].arrival) {
currentTime = tasks[taskIdx].arrival;
}
// Enqueue all tasks that have arrived by currentTime
while (taskIdx < n && tasks[taskIdx].arrival <= currentTime) {
readyQueue.push(tasks[taskIdx]);
taskIdx++;
}
// Pick the next highest priority task
Task currentTask = readyQueue.top();
readyQueue.pop();
currentTime += currentTask.processing;
completionTimes[currentTask.original_index] = currentTime;
}
return completionTimes;
}
};
Complexity Analysis
- Time Complexity: $\mathcal{O}(n \log n)$ Sorting the tasks takes $\mathcal{O}(n \log n)$ time. Each task is pushed into and popped from the priority queue exactly once, taking $\mathcal{O}(\log n)$ time per task. Thus, total time complexity is $\mathcal{O}(n \log n)$.
- Space Complexity: $\mathcal{O}(n)$ The priority queue and task structures require $\mathcal{O}(n)$ additional memory space.
Advertisement