Problem 621

Abstract / Summary

Given tasks with cooldown periods, find the minimum time to finish all tasks following the cooldown rule.

Note

The full problem description, including helpful examples, can be found on LeetCode.

Python 0621.py

class Solution:
    def leastInterval(self, tasks: List[str], n: int) -> int:
        freq = defaultdict(int)
        for t in tasks:
            freq[t] += 1

        tick = -n # all tasks may start at first tick, without delay
        step = 10**8
        # encoding: timestamp, priority, ASCII
        def deadline(t):
            return (tick + n) * step + (len(tasks) - freq[t]) * 10**3 + ord(t)

        next = []
        for f in freq:
            heappush(next, deadline(f))

        tick = 0
        while next:
            tick += 1
            current = next[0]
            # nothing ready ?
            if current > tick*step:
                continue

            # ready task, highest priority
            which = chr(current % 1000)
            freq[which] -= 1
            if freq[which] == 0:
                heappop(next)
            else:
                heapreplace(next, deadline(which))

            # postpone other ready tasks
            while next and next[0] < tick*step:
                heappushpop(next, next[0] + step)

        return tick
37 lines | 1105 bytes | submitted September 20, 2023 | GitHub   18% comment ratio | tab size 4 | low complexity

Difficulty

This problem is ranked as
easy medium hard

and has an acceptance rate of about 64% (1+ million submissions).

Typical patterns to solve this problem:
  • Array
  • Counting
  • Greedy
  • Hash Table
  • Heap (Priority Queue)
  • Sorting
I solved 663 easy, 1250 medium and 309 hard problems

AI Disclaimer

Every single line of code was written by me, 100% from scratch, between August 2023 and August 2024.
However, two years later I set up this website and used various AI tools to generate most of the walkthrough / explanations below.

See this dedicated page for more details.
LeetCode profile | GitHub repo

Code Walkthrough

Overview

The implementation solves the classic “task scheduler” problem: given a list of tasks identified by capital letters and a cooling interval n, it computes the minimum number of time units required to finish all tasks while respecting the cooling constraint. The algorithm builds a frequency map, encodes scheduling priorities into a single integer, and drives the execution with a min-heap that always yields the next eligible task. The design avoids explicit per-tick scanning of all task types and instead relies on heap operations to jump directly to the next executable moment.

Frequency Counting

The first phase tallies how many times each distinct task appears.

freq = defaultdict(int)
for t in tasks:
    freq[t] += 1

The defaultdict automatically creates a zero entry for unseen tasks, ensuring the loop runs in linear time with respect to the length of tasks. The resulting freq dictionary maps each task character to its remaining count.

Timestamp and Priority Encoding

A clever encoding packs three pieces of information-earliest possible execution time, remaining work order, and the task’s ASCII code-into a single integer.

tick = -n                     # allow first tasks to start at time 0
step = 10**8                  # large multiplier to separate time from priority
def deadline(t):
    return (tick + n) * step + (len(tasks) - freq[t]) * 10**3 + ord(t)
  • The term (tick + n) * step represents the earliest tick at which the task may run again, scaled by step.
  • (len(tasks) - freq[t]) * 10**3 provides a secondary priority that favors tasks with more remaining occurrences, ensuring a deterministic tie-breaker.
  • ord(t) distinguishes tasks with identical timestamps and priorities.

By using a single integer, the heap can compare tasks directly without custom comparator logic, which speeds up each heap operation.

Heap Initialization

All distinct tasks are inserted into the heap with their initial deadline values.

next = []
for f in freq:
    heappush(next, deadline(f))

Because the heap stores the encoded integers, the smallest value corresponds to the task that can be executed the soonest and, among equals, the one with higher remaining count.

Main Scheduling Loop

The core loop increments a global tick counter and extracts tasks that are ready to run.

tick = 0
while next:
    tick += 1
    current = next[0]
    if current > tick*step:
        continue
    which = chr(current % 1000)
    freq[which] -= 1
    if freq[which] == 0:
        heappop(next)
    else:
        heapreplace(next, deadline(which))
    while next and next[0] < tick*step:
        heappushpop(next, next[0] + step)
  • The condition current > tick*step checks whether the earliest deadline lies in the future; if so, the loop simply advances the tick without executing any task, effectively modeling idle periods.
  • When a task is ready, its character is recovered by chr(current % 1000). The frequency map is decremented, and the heap entry is either removed (if the task is exhausted) or updated with a new deadline reflecting the cooling interval.
  • The inner while loop handles the case where multiple tasks become ready at the same tick. It postpones any still-ready tasks by adding step to their encoded value, which pushes them forward by one time unit while preserving their relative priority.

Edge-Case Handling

When n equals zero, the cooling constraint disappears. The initial tick = -n becomes zero, and the deadline calculation reduces to a priority based solely on remaining count and ASCII code. The if current > tick*step guard never triggers, so the loop schedules tasks back-to-back without idle slots.

If the input contains only a single distinct task, the heap holds one element. After each execution the frequency is decremented until it reaches zero, at which point heappop removes the element and the loop terminates. The idle-time check correctly inserts the required gaps when n is positive, because the deadline for the same task is recomputed as tick + n.

When the number of tasks is less than n + 1, the algorithm naturally inserts idle ticks. The continue branch advances tick until the next task’s deadline becomes reachable, ensuring the final count includes all necessary idle intervals.

If multiple tasks share the same frequency, the ASCII component of the encoding provides a deterministic ordering, preventing nondeterministic behavior in the heap.

Performance Optimizations

The use of a min-heap guarantees that each extraction or update costs logarithmic time relative to the number of distinct task types, not the total number of tasks. This is a substantial improvement over naïve per-tick scanning, especially when the task list is large but the alphabet size is bounded (maximum 26 uppercase letters).

Encoding timestamp, priority, and ASCII into a single integer eliminates the need for tuple comparisons or custom comparator functions, which reduces overhead in the heap’s internal ordering logic.

The heappushpop operation inside the inner loop efficiently postpones ready tasks without performing separate push and pop calls, thereby saving one heap restructure per postponed task.

By initializing tick to -n, the algorithm avoids a special-case for the first execution; the first tasks can be scheduled at tick 1 without additional offset handling.

The large constant step = 10**8 separates the time component from the priority component by several orders of magnitude, ensuring that time dominates the comparison while still allowing the priority and ASCII bits to break ties. This design avoids floating-point arithmetic and keeps all operations in integer domain, which is faster on typical CPUs.

Result

After the heap becomes empty, all tasks have been scheduled respecting the cooling interval, and the variable tick holds the total number of time units elapsed. The function returns this value as the minimal schedule length.

written by openai gpt

ELI5 - Simplified Explanation

Imagine there is a big basket filled with colorful toy blocks that need to be put away. Each block has a letter on it. Some letters appear many times, and others only once. There is a rule that if you pick up a block with a specific letter, you have to wait for a little while before you can pick up another block with that same letter again.

The code works like a helper who decides which block to pick up next. First, it counts how many of each colored block there are in the basket. It wants to make sure all the blocks get put away as quickly as possible.

To keep track of when blocks can be used, the helper gives each block a special number. This number tells the helper two things: when the block is allowed to be picked up again and how many blocks of that color are still left in the basket. If a color has a lot of blocks waiting to be put away, the helper treats it as a high priority so it stays busy.

The helper uses a special list that always keeps the most important block right at the front. As time goes by tick by tick, the helper looks at the front of the list. If it is time to pick up a block, the helper takes that block, removes it from the basket, and recalculates when it can be used again. If it is not quite time yet to pick up a specific block, the helper waits until the right moment. The process continues until every single block has been put away safely.

written by google gemini

Code Quality

Several LLMs ranked my code quality on a scale from 1 (very poor) to 10 (outstanding):
n = 27 | median = 4 | μ = 4.56 | σ = 1.89

Complexity Analysis

not analyzed yet

Other Solutions

I wrote a basic crawler to find other solutions to that problem.
Obviously, I did that after I solved the problem myself.

In general, AlgoMonster and WalkCCC solutions are high-quality and always worth a look.
I haven't checked most of the other solutions, but they might be helpful as well.

And of course, the LeetCode forum is always a good place (or even the best) to look for alternative approaches.

(click on a column header to sort the table)
author
author solutions
www AlgoMonster C++ Python Java TS
WalkCCC C++ Python Java
leetcode.ca C++ Python Java C# Go
LeetcodeHelp C++ Python Java
Progiez C++ Python Java
github BigEggStudy C#
black-shadows C++
bujosa Python
charles-wangkai Java
everthis JS
fartem Ruby
FelixFan1122 C#
fengvyi C++
fpdjsns C++
gouthampradhan Java
JoshCrozier JS
keineahnung2345 C++
linouk23 C++
lkwq007 Python
MacJim C++
pruthvikp Python
ruoru Rust
sonugiri1043 C++
suxrobGM C#
trung-hn Python
varunu28 Java
vli02 C
vthwang Java
wisdompeak C++
yuanliu666 Kotlin
zfang399 C++
AlgoMonster C++, Python, Java, TypeScript
WalkCCC C++, Python, Java
leetcode.ca C++, C#, Go, Java, Python
LeetcodeHelp C++, Java, Python
Progiez C++, Java, Python
BigEggStudy C#
black-shadows C++, C++
bujosa Python
charles-wangkai Java
everthis JavaScript
fartem Ruby
FelixFan1122 C#
fengvyi C++
fpdjsns C++
gouthampradhan Java
JoshCrozier JavaScript
keineahnung2345 C++
linouk23 C++
lkwq007 Python
MacJim C++
pruthvikp Python
ruoru Rust
sonugiri1043 C++
suxrobGM C#
trung-hn Python
varunu28 Java
vli02 C
vthwang Java
wisdompeak C++
yuanliu666 Kotlin
zfang399 C++
44 links | 31 authors | 11 languages

Videos

10 videos   did my crawler come up with invalid videos ? drop me a line.

Similar Problems

4 similar problems

Solved On The Same Day

4 medium 1 hard
6 solutions   September 20, 2023

Extra

There are a few extra pages giving you some insights into my setup:
  • Tech Stack - my development environment and tools
  • AI - how AI helped me create this website (NOT solving the problems)
  • Progress - progression over time
  • Quality - code quality