The greedy choice
Identical tasks must be separated by at least n cooldown slots. The greedy insight: at every tick, run the task with the most remaining copies. Starve the bottleneck task and you'll be forced into idle time later, so tackle the busiest one first, always.
A max-heap keyed on remaining count gives you that pick in O(log 26). After running a task, it can't return immediately: park it in a cooldown queue with the time it becomes available again. Each tick, release anything whose cooldown has expired back into the heap.
time += 1
if heap: run most-frequent task
if count-1 > 0:
cooldown.push(task, time+n)
else: idle
release expired from cooldown → heap
T = total ticks (answer length). Each tick does O(log 26) heap work; at most 26 distinct task types.
Heap and cooldown queue together hold at most 26 task types.
Step-by-step trace
| # | Time | Action | Ran | Heap (count) | Cooling | Idles |
|---|
int leastInterval(vector<char>& tasks, int n) { vector<int> freq(26, 0); for (char t : tasks) freq[t - 'A']++; priority_queue<int> pq; // max-heap of remaining counts for (int f : freq) if (f) pq.push(f); int time = 0; queue<pair<int,int>> cool; // {remaining, availableAt} while (!pq.empty() || !cool.empty()) { time++; if (!pq.empty()) { int rem = pq.top() - 1; pq.pop(); // run most frequent if (rem > 0) cool.push({rem, time + n}); } // release a task whose cooldown just expired if (!cool.empty() && cool.front().second == time) { pq.push(cool.front().first); cool.pop(); } } return time; }