Single Threaded CPU

Medium Top 250
Associated Patterns
Interviewed At (Company Tags)
AmazonGoogle

Problem Description

You are given n tasks where tasks[i] = [enqueueTimei, processingTimei]. The CPU picks the available task with the shortest processing time (ties broken by original index). Return the order in which tasks are processed.


Examples

Example 1:

Input: tasks = [[1,2],[2,4],[3,2],[4,1]] Output: [0,2,3,1]

Example 2:

Input: tasks = [[7,10],[7,12],[7,5],[7,4],[7,2]] Output: [4,3,2,0,1]


Constraints

  • 1 ≤ tasks.length ≤ 10⁵
  • 1 ≤ enqueueTimei, processingTimei ≤ 10⁹

Simulate the CPU with a Min-Heap

Sort tasks by enqueue time (retaining original index). Simulate the clock: at each moment, add all tasks that have become available to a min-heap sorted by (processing time, original index). Pick the top, run it to completion, advance the clock by processing time, then add newly available tasks.

If the heap is empty when the CPU finishes a task, jump the clock forward to the next task’s enqueue time rather than waiting one unit at a time.


Solution: Sorted Tasks + Min-Heap Simulation

import java.util.*;

class Solution {
    public int[] getOrder(int[][] tasks) {
        int n = tasks.length;
        // pair tasks with their original index, then sort by enqueue time
        Integer[] indices = new Integer[n];
        for (int i = 0; i < n; i++) indices[i] = i;
        Arrays.sort(indices, (a, b) -> tasks[a][0] - tasks[b][0]);

        // min-heap: [processingTime, originalIndex]
        PriorityQueue<int[]> heap = new PriorityQueue<>(
            (a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]
        );

        int[] result = new int[n];
        long time = 0;
        int taskIdx = 0, resultIdx = 0;

        while (resultIdx < n) {
            // add all tasks available by current time
            while (taskIdx < n && tasks[indices[taskIdx]][0] <= time) {
                int oi = indices[taskIdx];
                heap.offer(new int[]{tasks[oi][1], oi});
                taskIdx++;
            }

            if (heap.isEmpty()) {
                // CPU idle: jump to next task's enqueue time
                time = tasks[indices[taskIdx]][0];
                continue;
            }

            int[] top = heap.poll();
            result[resultIdx++] = top[1];
            time += top[0]; // advance clock by processing time
        }
        return result;
    }
}
import heapq

class Solution:
    def getOrder(self, tasks: list[list[int]]) -> list[int]:
        # attach original index, sort by enqueue time
        indexed = sorted(enumerate(tasks), key=lambda x: x[1][0])

        heap = []  # (processing_time, original_index)
        result = []
        time = 0
        i = 0

        while len(result) < len(tasks):
            # add all tasks available at current time
            while i < len(indexed) and indexed[i][1][0] <= time:
                orig_idx, (enqueue, process) = indexed[i]
                heapq.heappush(heap, (process, orig_idx))
                i += 1

            if not heap:
                # CPU idle: jump to next task's enqueue time
                time = indexed[i][1][0]
                continue

            process_time, orig_idx = heapq.heappop(heap)
            result.append(orig_idx)
            time += process_time

        return result
#include <vector>
#include <queue>
#include <algorithm>
#include <numeric>

class Solution {
public:
    std::vector<int> getOrder(std::vector<std::vector<int>>& tasks) {
        int n = tasks.size();
        std::vector<int> indices(n);
        std::iota(indices.begin(), indices.end(), 0);
        std::sort(indices.begin(), indices.end(), [&](int a, int b) {
            return tasks[a][0] < tasks[b][0];
        });

        // min-heap: {processingTime, originalIndex}
        using T = std::pair<long long, int>;
        std::priority_queue<T, std::vector<T>, std::greater<T>> heap;

        std::vector<int> result;
        long long time = 0;
        int taskIdx = 0;

        while ((int)result.size() < n) {
            while (taskIdx < n && tasks[indices[taskIdx]][0] <= time) {
                int oi = indices[taskIdx++];
                heap.push({tasks[oi][1], oi});
            }

            if (heap.empty()) {
                time = tasks[indices[taskIdx]][0];
                continue;
            }

            auto [pt, oi] = heap.top(); heap.pop();
            result.push_back(oi);
            time += pt;
        }
        return result;
    }
};

Complexity Analysis:

  • Time Complexity: O(n log n). Sorting takes O(n log n). Each task is pushed and popped from the heap once, each O(log n).
  • Space Complexity: O(n) for the heap and indices array.

Where it breaks: use long long (or int64) for the clock. Enqueue times and processing times can each be up to 10⁹, and you sum them over up to 10⁵ tasks. The total time can reach 10¹⁴, overflowing a 32-bit integer.


Common Mistakes

  • Not advancing the clock to the next enqueue time when the heap is empty. Incrementing by 1 works but takes O(next_enqueue_time) iterations of the outer loop, which is unacceptably slow.
  • Using int for the time variable. Integer overflow silently corrupts the simulation.
  • Not preserving the original task index after sorting. After sorting by enqueue time, you lose the original order. Always carry the original index along.

Frequently Asked Questions

Why sort by enqueue time first? Tasks only become available at their enqueue time. Sorting lets you efficiently find which tasks have become available using a pointer that advances with time, rather than scanning all tasks on every step.

What does “same processing time” mean for the tie-breaking rule? When two available tasks have equal processing time, the CPU picks the one with the smaller original (0-indexed) task label. The (processingTime, originalIndex) tuple in the heap handles this automatically.


← All Problems