HEAP (PRIORITY QUEUE) › IMPLEMENTARY QUESTIONS
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →Take the task that appears most often, with frequency maxFreq. Between two consecutive copies of this task, at least n other intervals must pass, so the first maxFreq - 1 copies force maxFreq - 1 frames of length n + 1: one interval for the task itself and n intervals after it. The final copy does not need a cooling interval after it.
If several task labels share the maximum frequency, their copies can occupy the ends of the forced frames. With maxFreq copies and countMax tied labels, the final frame ends with countMax tasks, so the forced length is (maxFreq - 1) x (n + 1) + countMax. Every other task can fill an idle position inside those frames. If there are more other tasks than gaps, no idle time is needed and the answer is simply the number of tasks.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(tasks.length) | The input is scanned once to count frequencies and the fixed 26-entry frequency array is scanned once more. The second scan is constant work because the alphabet size is fixed, so no task is processed repeatedly. |
| Space | O(1) extra space | The frequency array always has 26 entries. The returned value is one integer rather than output storage, and no working structure grows with the input; the bound therefore stays O(1) even for the worst input shape. |
#include <algorithm>
#include <string>
using namespace std;
class Solution {
public:
int leastInterval(string tasks, int n) {
int freq[26] = {0};
for (char c : tasks) {
freq[c - 'A']++;
}
int maxFreq = 0;
int countMax = 0;
for (int f : freq) {
if (f > maxFreq) {
maxFreq = f;
countMax = 1;
} else if (f == maxFreq) {
countMax++;
}
}
int partCount = maxFreq - 1;
int partLength = n + 1;
int forcedLength = partCount * partLength + countMax;
return max(static_cast<int>(tasks.size()), forcedLength);
}
};The important placement is the final max call. The frame calculation counts every forced position around the most frequent tasks, but it does not promise that all remaining tasks fit inside those positions. When the remaining tasks are abundant, they occupy the gaps and extend the schedule beyond the frame lower bound; taking the maximum handles both the idle and no-idle cases.
A max heap gives the most frequent currently available task priority. Process time in cycles of n + 1 intervals: remove up to n + 1 different tasks from the heap, decrease their frequencies, and return unfinished tasks only after the cycle ends. Returning them later enforces the cooldown. This is a different arrangement, not an optimisation: it uses O(tasks.length) heap and cycle storage instead of the formula's O(1) extra space.
#include <algorithm>
#include <queue>
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
int leastInterval(string tasks, int n) {
int freq[26] = {0};
for (char c : tasks) {
freq[c - 'A']++;
}
priority_queue<int> available;
for (int f : freq) {
if (f > 0) {
available.push(f);
}
}
int elapsed = 0;
int cycleLength = n + 1;
while (!available.empty()) {
vector<int> used;
int cycleUsed = 0;
while (cycleUsed < cycleLength && !available.empty()) {
int remaining = available.top();
available.pop();
if (--remaining > 0) {
used.push_back(remaining);
}
cycleUsed++;
}
elapsed += cycleUsed;
if (!available.empty()) {
elapsed += cycleLength - cycleUsed;
}
for (int remaining : used) {
available.push(remaining);
}
}
return elapsed;
}
};