The problem
You are given an array of CPU tasks
, each labeled with a letter from A to Z, and a number n
. Each CPU interval can be idle or allow the completion of one task. Tasks can be completed in any order, but there’s a constraint: there has to be a gap of at least n
intervals between two tasks with the same label.
Return the minimum number of CPU intervals required to complete all tasks.
Examples
Input: tasks = ["A","A","A","B","B","B"], n = 2
Output: 8
Explanation: A possible sequence is: A -> B -> idle -> A -> B -> idle -> A -> B.
After completing task A, you must wait two intervals before doing A again. The same applies to task B. In the 3rd interval, neither A nor B can be done, so you idle. By the 4th interval, you can do A again as 2 intervals have passed.
Input: tasks = ["A","C","A","B","D","B"], n = 1
Output: 6
Explanation: A possible sequence is: A -> B -> C -> D -> A -> B.
With a cooling interval of 1, you can repeat a task after just one other task.
Input: tasks = ["A","A","A", "B","B","B"], n = 3
Output: 10
Explanation: A possible sequence is: A -> B -> idle -> idle -> A -> B -> idle -> idle -> A -> B.
There are only two types of tasks, A and B, which need to be separated by 3 intervals. This leads to idling twice between repetitions of these tasks.
Constraints
- 1 <= tasks.length <= 10^4
- tasks[i] is an uppercase English letter.
- 0 <= n <= 100
Explanation
To better understand the problem, let’s look at a visual representation of the first example.
We are given tasks = ["A","A","A","B","B","B"]
and n = 2
, and we need to find the minimum number of units of time to finish all of the tasks.
- If we processed one
A
, and after we try to process anotherA
we will have to waitn
units of time before we can do that. - Instead of processing the second
A
we can processB
, and remain idle1
unit of time and then we can process our secondA
and our secondB
, because our idle timen = 2
. As a result, we will return8
in our output.
Let’s look at another example with input AAA, BB, CC
and n = 1
.
- The first thing that we need to do is to keep track of the count of each character.
Next, we will need to figure out what character we will be processing first:
- If we have chosen to process, for example, the
C
character first, we will end up adding idle time to wait until we process allA
’s, and it will not be the minimum interval.
The more optimal way to process our characters is to start from the most frequent character, in our case it’s A
. This way we can process different characters and minimize the idle time.
Max Heap Solution
func leastInterval(_ tasks: [Character], _ n: Int) -> Int {
var count: [Character: Int] = [:]
for t in tasks {
count[t, default: 0] += 1
}
var maxHeap = Heap(count.values)
var time = 0
var q: Deque<[Int]> = Deque()
while !maxHeap.isEmpty || !q.isEmpty {
time += 1
if !maxHeap.isEmpty {
let cnt = maxHeap.removeMax() - 1
if cnt != 0 {
q.append([cnt, time + n])
}
}
if !q.isEmpty && q[0][1] == time {
maxHeap.insert(q.removeFirst()[0])
}
}
return time
}
Explanation
The optimal way to solve this problem is to use a max heap data structure. The max heap will help us determine what character is more frequent in O(logn)
time.
We will be using the time
property that will tell us the current time that we are at.
We will also be using a queue data structure to store our updated character count, and the time that will tell us when we can process this task again. The steps of the algorithm will look like these:
- We are going to pop the max value from our max heap, decrease it by
1
, and add it to the queue as the first value. - We are also going to calculate the time by getting our current time and adding
n
to it(1 + 1 = 2)
. After that, we will add it to our queue as a second value. - Next, we are going to increase our current time by
1
. - Now, we are at time
2
and we can see that task2, 2
is available to add back to our heap, so we are going to pop it from our queue and add it back to the heap.
Once time is 0
we are not going to do anything with it, because it does not have to be idle, so we are never going to add it back to our heap.
We are going to follow the same process until our heap or queue is not empty.
Time/ Space complexity
- Time complexity: O(m)
- Space complexity: O(1) since we have at most 26 different characters
- Where
m
is the number of tasks