🚀 Phases 1–4 are live — Days 1–13 + Day 17 are fully written. See the roadmap →

Task Scheduler medium

Description

You’re given a list of CPU tasks represented by characters, and an integer n representing the cooldown — the minimum time between two tasks of the same type. Each task takes one unit of time. Between two tasks of the same kind, the CPU can either run a different task or idle.

Return the minimum total time to finish all tasks.

Examples

> Case 1:
    Input:  tasks = ["A","A","A","B","B","B"], n = 2
    Output: 8
    # One valid schedule: A B idle A B idle A B
    # (after each A, must wait 2 ticks before the next A)
 
> Case 2:
    Input:  tasks = ["A","C","A","B","D","B"], n = 1
    Output: 6
    # A B A C D B  (or other reorderings — no idle slots needed)
 
> Case 3:
    Input:  tasks = ["A","A","A","B","B","B"], n = 0
    Output: 6
    # n = 0 → no cooldown, schedule any order

Constraints

  • 1 <= tasks.length <= 10^4
  • 0 <= n <= 100

The greedy intuition

At every clock tick, ask: of the tasks I’m allowed to run (not on cooldown), which has the highest remaining count? Run that one. If nothing is available, idle.

The “highest remaining count” → max-heap query. The “not on cooldown” constraint → tasks come off cooldown after n more ticks → a queue of waiting tasks with their available time.

This is the exact same shape as Reorganize String, just with a configurable cooldown instead of “1”.

Approach 1: Max-heap + cooldown queue (simulation)

counts ← Counter(tasks)
heap ← max-heap of counts
cooldown ← queue of (count_remaining, ready_time)
time ← 0

while heap is non-empty or cooldown is non-empty:
    time += 1
    if heap is non-empty:
        c ← pop heap                       # run one
        if c > 1: cooldown.push((c - 1, time + n))
    while cooldown is non-empty and cooldown.front().ready_time == time:
        heap.push(cooldown.popleft().count_remaining)
return time

The cooldown queue is always sorted by ready_time because we insert with ready_time = time + n and we only insert in time order. So we never need a heap for the cooldown — a plain FIFO queue is enough.

Approach 2: The closed-form math trick

An O(n) solution that doesn’t even need a heap:

Let max_count = the most frequent task’s count, and t = the number of tasks tied for that count. The minimum schedule length is:

answer = max(len(tasks), (max_count - 1) · (n + 1) + t)

Intuition. Place the most-frequent task at positions 0, n+1, 2(n+1), ... — that’s max_count - 1 gaps of size n + 1. In each gap, fill with other tasks (or idle). The last “row” has t slots (one for each task tied at max_count). And the answer can never be less than len(tasks), since that’s the bare minimum work.

def least_interval_math(tasks, n):
    counts = Counter(tasks)
    max_count = max(counts.values())
    tied = sum(1 for v in counts.values() if v == max_count)
    return max(len(tasks), (max_count - 1) * (n + 1) + tied)

This is the interview-winning answer — O(n) time, O(k) space, a single max-over-formula evaluation. The heap simulation is correct but unnecessary here.

Why the math works

Think of the schedule as a grid with max_count rows. The most-frequent task occupies one slot per row. Each row has n + 1 slots (the slot itself + n cooldown slots). With max_count - 1 complete rows + a partial last row of size t, the total slot count is (max_count - 1) · (n + 1) + t. If we have more tasks than fit in this grid, the extras can fill in the cooldown gaps and the answer is just len(tasks).

Analysis

ApproachTimeSpace
Heap simulationO(N log k)O(k)
Math formulaO(N)O(k)

where N = tasks.length and k = number of distinct task types (≤ 26).

When to prefer which

  • Math formula: when you’ve recognized the pattern and trust your reasoning. Beats the heap on time and code size.
  • Heap simulation: when the problem extends to “tasks have different costs,” “multiple machines,” or “dynamic arrivals” — the formula breaks but the heap approach generalizes naturally.

The math formula is the answer for this problem. The heap approach is the template for the family of cooldown / capacity / multi-machine variants that build on it.