DSA SheetMedium

HEAP (PRIORITY QUEUE)IMPLEMENTARY QUESTIONS

Task Scheduler

MediumEditorial · 8 minGenerated by gpt-5.6-luna · Aug 25

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 →

Intuitionwhy the most frequent task controls the schedule

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.

Cooling frames formed by the most frequent tasksThe picture shows repeated copies of the most frequent task arranged from left to right. Each copy except the last begins a frame of n plus 1 positions: the maximum-frequency task occupies the first position, and the remaining positions are gaps for different tasks or idle intervals. When several labels have the same maximum frequency, the last frame ends with those tied tasks side by side. The picture makes clear that the frame length is a lower bound, but extra tasks can remove every idle position.ACBADBAidleBABFixed frames from maximum-frequency tasksf = 4 n = 2 countMax = 2first maximum taskmiddle frame gaps: 2 slots eachlast maximum tasks tied: A, BC and D fill gap positions; B is another tied maximum taskidle marks an unavoidable intervallower bound = (f − 1)(n + 1) + countMax = 11 slotsOther tasks can fill the gaps, but cannot remove the required separation.
The frame lower bound becomes exact unless the other tasks fill more intervals than the gaps.

Approach

  1. Count the frequency of each uppercase letter in an array of 26 entries, because only the number of copies of each label affects the minimum schedule length.
  2. Scan the frequencies while tracking maxFreq and countMax, resetting countMax when a larger frequency appears and incrementing it on a tie; both values are needed to describe the forced frames correctly.
  3. Compute partCount as maxFreq - 1, because only the gaps before the final copy require cooling, and compute partLength as n + 1, because each such frame contains one maximum task plus n separating intervals.
  4. Set total to partCount x partLength + countMax, because the first maxFreq - 1 frames are full and the final frame contains one task for each label tied at maxFreq.
  5. Return the larger of tasks.size() and total, because the frame arrangement is a lower bound, while a schedule cannot finish in fewer intervals than the number of actual tasks; extra tasks may fill all the gaps and make the lower bound smaller than the task count.

Complexity

MEASUREBOUNDWHY
TimeO(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.
SpaceO(1) extra spaceThe 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.

Annotated solutionC++ · formula · constant extra space

CPPCount the maximum-frequency frames, then clamp the lower bound by the number of tasks.
#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.

The heap alternativea direct simulation when you want the schedule itself

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.

CPPSimulate cooldown cycles with a max heap and add idle intervals only before unfinished work.
#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;
    }
};

Common mistakestwo wrong formula shapes that look plausible

Previous · Distant Barcodes