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
Difficulty
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
AI Disclaimer
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.
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) * steprepresents the earliest tick at which the task may run again, scaled bystep. (len(tasks) - freq[t]) * 10**3provides 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*stepchecks 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
whileloop handles the case where multiple tasks become ready at the same tick. It postpones any still-ready tasks by addingstepto 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.
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.
Code Quality
Complexity Analysis
Other Solutions
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++ |
Videos
Similar Problems
- problem 358: Rearrange String k Distance Apart
- problem 767: Reorganize String
- problem 1953: Maximum Number of Weeks for Which You Can Work
- problem 2365: Task Scheduler II
Solved On The Same Day
- problem 636: Exclusive Time of Functions
- problem 676: Implement Magic Dictionary
- problem 677: Map Sum Pairs
- problem 1658: Minimum Operations to Reduce X to Zero
- problem 710: Random Pick with Blacklist
Extra
- 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