Facebook Pixel

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:

  1. Count the frequency of each task using Counter(tasks)
  2. Find the maximum frequency x among all tasks
  3. Count how many tasks have this maximum frequency s
  4. The minimum intervals needed is the maximum of:
    • Total number of tasks: len(tasks)
    • Formula based on maximum frequency: (x - 1) * (n + 1) + s

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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 frequency
    • max(..., 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 Evaluator

Example 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 where k 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 is O(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 and s use O(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 frequency s = 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More