Task Scheduler
Master this topic with zero to advance depth.
Task Scheduler
Given a characters array tasks, representing the tasks a CPU needs to do, where each letter represents a different task. For each unit of time, the CPU could complete either one task or stay idle.
There is a cooldown period n between two same tasks, meaning at least n units of time must pass between any two executions of the same task.
Return the least number of units of time to finish all tasks.
Visual Representation
tasks = ["A","A","A","B","B","B"], n = 2
Timeline:
A -> B -> idle -> A -> B -> idle -> A -> B
1 2 3 4 5 6 7 8
Total Time: 8Examples
A possible sequence: A -> B -> idle -> A -> B -> idle -> A -> B.
Level I: Simple Simulation
Intuition
At each unit of time, we want to pick the most frequent task that is currently available (not in cooldown). We can simulate this step-by-step.
Thought Process
- Count frequencies of all tasks.
- Maintain an array
nextAvailableTimefor each task. - At each time
t, pick the available task with the highest remaining frequency. - If no task is available, stay idle and increment time.
Detailed Dry Run
tasks=[A,A,B], n=2 t=0: best=A (count 2). nextAvail[A]=3, rem=2. t=1: best=B (count 1). nextAvail[B]=4, rem=1. t=2: no one avail (A needs t=3). idle. t=3: best=A (count 1). nextAvail[A]=6, rem=0. Total: 4 units.
Level II: Greedy with Max-Heap
Intuition
Instead of scanning all tasks every time, we use a Max-Heap to always pick the highest frequency task. Tasks in cooldown are stored in a temporary wait-queue and added back to the heap once their cooldown expires.
Thought Process
- Push all task frequencies into a Max-Heap.
- Use a Queue to store
{remaining_freq, release_time}for tasks in cooldown. - At each unit of time:
- Check the Queue: If a task's
release_time <= current_time, push it back to the Heap. - Pop the highest frequency task from the Heap, decrement its frequency, and if it's still > 0, add it to the wait-queue with
release_time = current_time + n + 1.
- Check the Queue: If a task's
Pattern: Priority Queue Simulation
Detailed Dry Run
tasks=[A,A,A,B,B,B], n=2 Heap: [(A,3), (B,3)] t=0: Pop (A,3). Add to waitQueue: (A,2) at t=3. time=1. t=1: Pop (B,3). Add to waitQueue: (B,2) at t=4. time=2. t=2: Heap empty. Wait till t=3... time=3. t=3: WaitQueue release (A,2). Heap: [(A,2)]...
Level III: Optimal Frequency Analysis (Math)
Intuition
The total time is bounded by the most frequent task. If 'A' is the most frequent (frequency ), we have blocks. Each of the first blocks must be followed by slots (either other tasks or idle).
Thought Process
- Let
max_fbe the maximum frequency. - The base structure is
(max_f - 1) * (n + 1)slots. - Any other tasks with the same
max_ffrequency will fill one extra slot at the very end. - Total time =
(max_f - 1) * (n + 1) + num_tasks_with_max_f. - If this result is smaller than the total number of tasks, it means no idle slots are needed, so the answer is
tasks.length.
Pattern: Math / Frequency Analysis
Detailed Dry Run
tasks = [A,A,A,B,B,B], n = 2
| Variable | Value | Explanation |
|---|---|---|
| maxFreq | 3 | 'A' and 'B' both appear 3 times |
| countMax | 2 | Two tasks (A, B) have max frequency |
| Formula | (3-1)*(2+1) + 2 | 2 * 3 + 2 = 8 |
| Final | max(8, 6) | 8 |
Visualization:
[A, B, _] [A, B, _] [A, B] -> Total 8 slots.
Found an issue or have a suggestion?
Help us improve! Report bugs or suggest new features on our Telegram group.