621. Task Scheduler
Medium
Problem:
There are tasks represented from A to Z. At each interval, the CPU can execute only one task, and the same task cannot be executed within 'n' intervals. If no further tasks can be executed, the CPU enters an idle state. Output the minimum number of intervals required to execute all tasks.
Input: tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
Output: 16
Explanation:
One possible solution is
A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> idle -> idle -> A -> idle -> idle -> Ahttps://leetcode.com/problems/task-scheduler/
What to learn:
collections.Counter()
A notable feature of the Counter is that it also handles zeros and negative numbers. Thus, adding an empty collection.Counter() will completely remove items with counts of zero or less from the list.
most_common()
The most_common() function outputs the items with the highest count first, essentially serving the same role as a maximum heap. However, the items are not extracted using pop().
Solution:
A priority queue should be used to extract the items with the highest count one by one, but the challenge is not to extract everything at once. Instead, we need a structure that allows us to update the count after only one extraction.
Consider the tasks:
Here, task "A" appears most frequently. Our goal is to minimize idle times, which means we need to schedule "A" in a manner that we don't end up in a position where we're forced to be idle waiting to schedule it again.
1. Initial State:
counter={A:4, B:2, C:1, D:1, E:1}result= 0
2. First Iteration:
We fetch the top n + 1 = 3 most frequent tasks. Why 3? Because within a span of 3 time units, we can perform the same task just once (i.e., the task "A" in this case) due to the cooldown period.
counter.most_common(3)returns[("A", 4), ("B", 2), ("C", 1)].We schedule these tasks in the order they're returned:
"A" -
counterbecomes{A:3, B:2, C:1, D:1, E:1}."B" -
counterbecomes{A:3, B:1, C:1, D:1, E:1}."C" -
counterbecomes{A:3, B:1, D:1, E:1}.
Our schedule now is: A -> B -> C.
No two adjacent tasks are the same, which ensures the cooldown period is maintained.
resultis incremented by 3.
3. Second Iteration:
We again fetch the top n + 1 = 3 tasks.
counter.most_common(3)returns[("A", 3), ("B", 1), ("D", 1)].Schedule:
"A" -
counterbecomes{A:2, B:1, D:1, E:1}."B" -
counterbecomes{A:2, D:1, E:1}."D" -
counterbecomes{A:2, E:1}.
Schedule now: A -> B -> C -> A -> B -> D.
Again, no two adjacent tasks are the same.
resultincrements by 3 and becomes 6.
4. Third Iteration:
We fetch top 3 tasks.
counter.most_common(3)returns[("A", 2), ("E", 1)]. Notice we have only 2 tasks this time.Schedule:
"A" -
counterbecomes{A:1, E:1}."E" -
counterbecomes{A:1}.
Schedule: A -> B -> C -> A -> B -> D -> A -> E.
Now, this is important:
We scheduled 2 tasks, but our slot size based on cooldown is 3 (remember,
n + 1). Hence, to ensure the next "A" isn't scheduled within the cooldown period, we need an idle time.
Schedule becomes: A -> B -> C -> A -> B -> D -> A -> E -> idle.
resultincrements by 3 (2 tasks + 1 idle).
5. Fourth Iteration:
Only "A" remains.
Schedule "A".
Final schedule: A -> B -> C -> A -> B -> D -> A -> E -> idle -> A.
resultbecomes 10.
In essence, by always trying to schedule the top n + 1 tasks, we're ensuring two things:
We're optimizing our task execution order to minimize idle times.
We're inherently ensuring that no two same tasks are adjacent because, in a span of
n + 1time units, we're executing the same task only once.
Last updated