150NeetCode 150

68.Task Scheduler

MediumHeap / Priority Queue · 5/7

Problem

You are given an array of CPU tasks tasks, each labeled with an uppercase letter, and a non-negative integer n. Each time unit the CPU either runs one task or stays idle. Two runs of the same task label must be separated by at least n time units.

Tasks may be executed in any order. Return the minimum number of time units needed to finish all tasks.

Examples

Input:  tasks = ["A","A","A","B","B","B"], n = 2
Output: 8
Explanation: A -> B -> idle -> A -> B -> idle -> A -> B
Input:  tasks = ["A","C","A","B","D","B"], n = 1
Output: 6
Explanation: A -> B -> C -> D -> A -> B. No idle needed.
Input:  tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
Output: 16
Explanation: A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> idle -> idle -> A -> idle -> idle -> A

Constraints

  • 1 <= tasks.length <= 10^4
  • tasks[i] is an uppercase English letter.
  • 0 <= n <= 100

💡 Key Insight

Only the counts of each letter matter. The most frequent task dictates the skeleton: with frequency maxFreq, it creates maxFreq - 1 full "frames" of length n + 1, plus a final partial frame containing every task that also has frequency maxFreq. Everything else fills the idle slots; if there are more tasks than slots, there is no idle time at all and the answer is simply tasks.length.

Brute Force (Greedy Simulation per Time Unit)

TimeO(T · 26) where T is the answer (at most tasks.length · (n + 1)).SpaceO(1)

Intuition

Simulate the CPU clock. At each time unit, among the tasks that are off cooldown, run the one with the most remaining occurrences (running frequent tasks early avoids being stuck with only one task type at the end). If nothing is available, idle.

Algorithm

  1. Count each letter into count[26]; nextFree[26] = 0.
  2. time = 0, remaining = tasks.length.
  3. While remaining > 0: find best with count > 0, nextFree <= time and maximum count.
    • If found: count[best]--, nextFree[best] = time + n + 1, remaining--.
    • time++ (whether or not a task ran).
  4. Return time.

Visualization

counts A=3, B=3, n=2
t=0: A,B ready  -> run A (nextFree A = 3)
t=1: B ready    -> run B (nextFree B = 4)
t=2: none ready -> idle
t=3: A ready    -> run A
t=4: B          -> run B
t=5: idle
t=6: A, t=7: B  -> time = 8

Code

java
class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] count = new int[26];
        for (char t : tasks) {
            count[t - 'A']++;
        }
        int[] nextFree = new int[26]; // earliest time each task may run again
        int remaining = tasks.length;
        int time = 0;
        while (remaining > 0) {
            int best = -1;
            for (int i = 0; i < 26; i++) {
                if (count[i] > 0 && nextFree[i] <= time && (best == -1 || count[i] > count[best])) {
                    best = i;
                }
            }
            if (best != -1) {
                count[best]--;
                nextFree[best] = time + n + 1;
                remaining--;
            }
            time++; // a task or an idle slot consumed this unit
        }
        return time;
    }
}

Complexity

  • Time: O(T · 26) where T is the answer (at most tasks.length · (n + 1)).
  • Space: O(1) — two arrays of size 26.

Complexity Comparison

Approach Time Space
Greedy Simulation O(T · 26) O(1)
Max-Heap + Cooldown Queue O(T) O(1)
Counting Frames O(m) O(1)

Common Pitfalls

  • Forgetting the max(tasks.length, ...) — with many distinct tasks (e.g. n = 1, six different letters) the formula alone underestimates.
  • Counting only one task in the last frame when several letters tie for maxFreq.
  • Cooldown off-by-one: a task run at time t may run again at t + n + 1, not t + n.
  • In the simulation, picking any ready task instead of the one with the most remaining — that can strand the frequent task at the end and add unnecessary idles.