621. Task Scheduler
Problem Description
You have an array of CPU tasks where each task is labeled with a letter from 'A' to 'Z'. You also have a cooling period n
that represents the minimum number of intervals that must pass between executing two tasks with the same label.
During each CPU interval, you can either:
- Execute one task
- Remain idle
The key constraint is that if you execute a task with label 'A', you cannot execute another task with label 'A' until at least n
intervals have passed.
For example, if tasks = ['A', 'A', 'B']
and n = 2
:
- One valid execution could be:
A → B → idle → A
(4 intervals total) - The gap between the two 'A' tasks is 2 intervals (B and idle), which satisfies the cooling period requirement
Your goal is to find the minimum number of CPU intervals needed to complete all tasks while respecting the cooling period constraint. Tasks can be executed in any order you choose.
The solution uses a mathematical approach:
- Count the frequency of each task using
Counter(tasks)
- Find the maximum frequency
x
among all tasks - Count how many tasks have this maximum frequency
s
- The minimum intervals needed is the maximum of:
- Total number of tasks:
len(tasks)
- Formula based on maximum frequency:
(x - 1) * (n + 1) + s
- Total number of tasks:
The formula (x - 1) * (n + 1) + s
represents the scenario where the most frequent tasks determine the schedule. We create (x - 1)
blocks of size (n + 1)
to ensure proper cooling periods, plus s
tasks at the end for the final occurrence of the most frequent tasks.
Intuition
The key insight is to think about which tasks create the bottleneck in our scheduling. The tasks that appear most frequently will force us to have idle time due to the cooling period constraint.
Consider the task that appears most frequently - let's say it appears x
times. This task needs to be scheduled with at least n
intervals between each occurrence. We can visualize this as creating "blocks" or "frames" where each block has size (n + 1)
:
Block 1: [MostFreqTask, _, _, ..., _] (n+1 slots) Block 2: [MostFreqTask, _, _, ..., _] (n+1 slots) ... Block (x-1): [MostFreqTask, _, _, ..., _] (n+1 slots) Last part: [MostFreqTask]
We need (x - 1)
complete blocks of size (n + 1)
each, which gives us (x - 1) * (n + 1)
intervals. The last occurrence of the most frequent task(s) doesn't need a cooling period after it.
But what if multiple tasks have the same maximum frequency? If s
tasks all appear x
times, they all need to be placed in that final position after the last block. This adds s
to our formula: (x - 1) * (n + 1) + s
.
However, there's another scenario to consider: what if we have so many different tasks that we can fill all the idle slots? In this case, we won't need any idle time at all, and the minimum number of intervals would simply be the total number of tasks len(tasks)
.
Therefore, the answer is the maximum of these two scenarios:
len(tasks)
: when we have enough variety to avoid idle time(x - 1) * (n + 1) + s
: when the most frequent tasks create unavoidable idle periods
The formula elegantly captures both cases - when we have many diverse tasks, len(tasks)
will be larger; when we have a few very frequent tasks, the formula based on maximum frequency will dominate.
Learn more about Greedy, Sorting and Heap (Priority Queue) patterns.
Solution Approach
The implementation follows a straightforward approach using frequency counting and mathematical calculation:
-
Count Task Frequencies: Use Python's
Counter
to count the frequency of each task type:cnt = Counter(tasks)
This creates a dictionary where keys are task labels and values are their frequencies.
-
Find Maximum Frequency: Determine the highest frequency among all tasks:
x = max(cnt.values())
This identifies which task(s) appear most often and will potentially create the scheduling bottleneck.
-
Count Tasks with Maximum Frequency: Count how many different tasks have this maximum frequency:
s = sum(v == x for v in cnt.values())
This uses a generator expression to count all tasks whose frequency equals
x
. -
Calculate Minimum Intervals: Apply the formula to determine the minimum number of CPU intervals:
return max(len(tasks), (x - 1) * (n + 1) + s)
Breaking down the formula:
(x - 1) * (n + 1)
: Represents the intervals needed for the first(x - 1)
occurrences of the most frequent tasks, where each occurrence requires a block of(n + 1)
intervals to maintain the cooling period+ s
: Adds the final occurrence of all tasks that have maximum frequencymax(..., len(tasks))
: Ensures we never return fewer intervals than the total number of tasks
The algorithm has a time complexity of O(m)
where m
is the number of unique tasks (at most 26 for A-Z), and space complexity of O(m)
for the counter. Since the number of unique tasks is bounded by 26, this is effectively O(1)
for both time and space in terms of the alphabet constraint.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through an example with tasks = ['A', 'A', 'A', 'B', 'B', 'C']
and n = 2
.
Step 1: Count Task Frequencies Using Counter, we get:
- 'A': 3 occurrences
- 'B': 2 occurrences
- 'C': 1 occurrence
Step 2: Find Maximum Frequency
The maximum frequency x = 3
(task 'A' appears 3 times)
Step 3: Count Tasks with Maximum Frequency
Only task 'A' has frequency 3, so s = 1
Step 4: Apply the Formula
Calculate (x - 1) * (n + 1) + s
:
(3 - 1) * (2 + 1) + 1
= 2 * 3 + 1
= 7
Compare with total tasks: len(tasks) = 6
The minimum intervals needed is max(6, 7) = 7
Visualizing the Schedule: We can arrange the tasks as follows:
Block 1: [A, B, C] (3 slots, cooling period satisfied) Block 2: [A, B, idle] (3 slots, cooling period satisfied) Final: [A] (last occurrence of most frequent task)
The schedule would be: A → B → C → A → B → idle → A
This gives us exactly 7 intervals. Task 'A' appears at positions 1, 4, and 7, maintaining the required cooling period of 2 intervals between each occurrence (positions 4-1=3 and 7-4=3, both ≥ n+1=3).
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def leastInterval(self, tasks: List[str], n: int) -> int:
6 # Count frequency of each task
7 task_counts = Counter(tasks)
8
9 # Find the maximum frequency among all tasks
10 max_frequency = max(task_counts.values())
11
12 # Count how many tasks have the maximum frequency
13 num_max_frequency_tasks = sum(1 for count in task_counts.values() if count == max_frequency)
14
15 # Calculate minimum time needed
16 # Formula explanation:
17 # - (max_frequency - 1) * (n + 1): Time for all complete cycles
18 # Each cycle contains n+1 slots, and we need max_frequency-1 complete cycles
19 # - num_max_frequency_tasks: Additional time for the last occurrence of max frequency tasks
20 # - We take max with len(tasks) because we might be able to fill all idle slots with other tasks
21 min_time = max(len(tasks), (max_frequency - 1) * (n + 1) + num_max_frequency_tasks)
22
23 return min_time
24
1class Solution {
2 public int leastInterval(char[] tasks, int n) {
3 // Array to store frequency count of each task (A-Z mapped to 0-25)
4 int[] taskFrequencies = new int[26];
5 int maxFrequency = 0;
6
7 // Count frequency of each task and find the maximum frequency
8 for (char task : tasks) {
9 int index = task - 'A';
10 taskFrequencies[index]++;
11 maxFrequency = Math.max(maxFrequency, taskFrequencies[index]);
12 }
13
14 // Count how many tasks have the maximum frequency
15 int tasksWithMaxFrequency = 0;
16 for (int frequency : taskFrequencies) {
17 if (frequency == maxFrequency) {
18 tasksWithMaxFrequency++;
19 }
20 }
21
22 // Calculate minimum intervals needed
23 // Formula: max of (total tasks, slots needed for most frequent tasks)
24 // Slots needed = (maxFrequency - 1) * (cooling period + 1) + tasks with max frequency
25 int slotsForMaxFreqTasks = (maxFrequency - 1) * (n + 1) + tasksWithMaxFrequency;
26 return Math.max(tasks.length, slotsForMaxFreqTasks);
27 }
28}
29
1class Solution {
2public:
3 int leastInterval(vector<char>& tasks, int n) {
4 // Count frequency of each task (A-Z mapped to 0-25)
5 vector<int> taskFrequency(26);
6 int maxFrequency = 0;
7
8 // Calculate frequency for each task and find the maximum frequency
9 for (char task : tasks) {
10 int taskIndex = task - 'A';
11 ++taskFrequency[taskIndex];
12 maxFrequency = max(maxFrequency, taskFrequency[taskIndex]);
13 }
14
15 // Count how many tasks have the maximum frequency
16 int maxFrequencyCount = 0;
17 for (int frequency : taskFrequency) {
18 if (frequency == maxFrequency) {
19 ++maxFrequencyCount;
20 }
21 }
22
23 // Calculate minimum intervals needed
24 // Formula: max(total tasks, (maxFreq - 1) * (cooldown + 1) + tasks with maxFreq)
25 // The first part handles cases where we have enough variety of tasks
26 // The second part handles cases where we need idle time due to cooldown
27 int minimumSlots = (maxFrequency - 1) * (n + 1) + maxFrequencyCount;
28
29 return max(static_cast<int>(tasks.size()), minimumSlots);
30 }
31};
32
1function leastInterval(tasks: string[], n: number): number {
2 // Count frequency of each task (A-Z mapped to 0-25)
3 const taskFrequency: number[] = new Array(26).fill(0);
4 let maxFrequency: number = 0;
5
6 // Calculate frequency for each task and find the maximum frequency
7 for (const task of tasks) {
8 const taskIndex: number = task.charCodeAt(0) - 'A'.charCodeAt(0);
9 taskFrequency[taskIndex]++;
10 maxFrequency = Math.max(maxFrequency, taskFrequency[taskIndex]);
11 }
12
13 // Count how many tasks have the maximum frequency
14 let maxFrequencyCount: number = 0;
15 for (const frequency of taskFrequency) {
16 if (frequency === maxFrequency) {
17 maxFrequencyCount++;
18 }
19 }
20
21 // Calculate minimum intervals needed
22 // Formula: max(total tasks, (maxFreq - 1) * (cooldown + 1) + tasks with maxFreq)
23 // The first part handles cases where we have enough variety of tasks
24 // The second part handles cases where we need idle time due to cooldown
25 const minimumSlots: number = (maxFrequency - 1) * (n + 1) + maxFrequencyCount;
26
27 return Math.max(tasks.length, minimumSlots);
28}
29
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the tasks array.
- Creating the Counter takes
O(n)
time as it needs to iterate through all tasks once - Finding the maximum value in the Counter takes
O(k)
time wherek
is the number of unique tasks (at most 26 for uppercase letters) - Counting how many tasks have the maximum frequency also takes
O(k)
time - The max operation and arithmetic calculations are
O(1)
- Since
k ≤ 26
is a constant, the overall time complexity isO(n)
Space Complexity: O(1)
or O(k)
where k
is the number of unique tasks.
- The Counter stores at most
k
unique tasks and their frequencies - Since tasks are represented as uppercase letters (A-Z),
k ≤ 26
, which is a constant - Variables
x
ands
useO(1)
space - Therefore, the space complexity is
O(1)
considering the bounded input constraint
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding Why We Use max(len(tasks), formula)
Pitfall: Many developers struggle to understand why we need to take the maximum between len(tasks)
and the formula result. They might think the formula alone should always give the correct answer.
Why it happens: The formula (x - 1) * (n + 1) + s
calculates the minimum intervals needed when the most frequent tasks create a bottleneck. However, when we have many different tasks with relatively low frequencies, we can fill all the idle slots with other tasks, making the total time equal to just the number of tasks.
Example where formula alone fails:
tasks = ['A', 'A', 'B', 'B', 'C', 'C', 'D', 'D', 'E', 'E', 'F', 'F']
,n = 2
- Max frequency
x = 2
, tasks with max frequencys = 6
- Formula gives:
(2 - 1) * (2 + 1) + 6 = 3 + 6 = 9
- But
len(tasks) = 12
, which is the correct answer - We can arrange as:
A → B → C → A → B → C → D → E → F → D → E → F
(no idle time needed)
Solution: Always remember that when you have enough task variety, you can avoid idle time completely, so the minimum intervals needed is at least the total number of tasks.
2. Off-by-One Error in Understanding the Formula Components
Pitfall: Confusing why we use (x - 1)
instead of x
in the formula, or why we add s
at the end.
Why it happens: The formula structure isn't immediately intuitive. People might think we need x
blocks of size (n + 1)
.
Correct understanding:
- We arrange tasks in blocks of size
(n + 1)
to ensure cooling period - We need only
(x - 1)
complete blocks because the last occurrence doesn't need a cooling period after it - The
s
represents the final occurrence of all max-frequency tasks
Visual example with tasks = ['A', 'A', 'A', 'B', 'B', 'B']
, n = 2
:
Block 1: A B _ (size n+1 = 3) Block 2: A B _ (size n+1 = 3) Final: A B (s = 2 tasks) Total: (3-1) * 3 + 2 = 8 intervals
3. Incorrectly Calculating the Number of Max-Frequency Tasks
Pitfall: Using incorrect logic to count tasks with maximum frequency, such as:
# Wrong approach 1: Counting occurrences instead of unique tasks
s = task_counts[max_frequency] # This would try to use frequency as a key
# Wrong approach 2: Counting total occurrences
s = sum(count for count in task_counts.values() if count == max_frequency)
Correct approach:
# Count how many different task types have the maximum frequency
s = sum(1 for count in task_counts.values() if count == max_frequency)
Example: If tasks = ['A', 'A', 'A', 'B', 'B', 'B']
:
- Max frequency is 3
- Number of tasks with max frequency is 2 (both 'A' and 'B')
- Not 3 (the frequency itself) or 6 (total occurrences)
4. Assuming the Algorithm Works Only for Single Characters
Pitfall: Thinking the solution only works for single-character task labels ('A' to 'Z').
Reality: The algorithm works for any hashable task identifiers. The Counter and formula don't depend on tasks being single characters. You could have:
tasks = ["task1", "task1", "task2", "task3", "task3", "task3"]
The algorithm would work exactly the same way. The 26-letter constraint is just a problem specification detail, not a limitation of the algorithm itself.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Want a Structured Path to Master System Design Too? Don’t Miss This!