2Brute2Best
🔥0
06

Heap-assisted greedy (Greedy Sub-pattern)

The best next choice depends on what you did so far, so keep the candidates in a heap (or an ordered map) and pop the current best each time.

Signals in the problem statement
  • ›

    "no two adjacent characters are equal"

  • ›

    "split into groups of consecutive cards"

  • ›

    "cooldown between equal tasks"

  • ›

    "merge the two smallest", "minimise max − min"

What makes it different

Most frequent first (Reorganize String) → max-heap by count. Smallest card first (Hand of Straights) → ordered map. Two smallest (Huffman) → min-heap. Lower the maximum (Minimize Deviation) → max-heap, track the min.

Questions

ordered from “learn the idea” to “recognise it without help”
  1. 1
    Reorganize String
    Basic Anchor·LeetCode #767
    Medium
  2. 2
    Hand of Straights
    Basic Reinforcement·LeetCode #846
    Medium
  3. 3
    Task Scheduler
    Slight Variation·LeetCode #621
    Medium
  4. 4
    Huffman Coding
    Medium·GeeksforGeeks
    Medium
  5. 5
    Minimize Deviation in Array
    Pattern Transfer·LeetCode #1675
    Hard

Template

heap-greedy-template.cpp
// Repeatedly merge the two smallest (Huffman cost, connecting ropes).
long long mergeTwoSmallest(const vector<int>& w) {
    priority_queue<long long, vector<long long>, greater<long long>> pq(w.begin(), w.end());
    long long cost = 0;
    while (pq.size() > 1) {
        long long a = pq.top(); pq.pop();
        long long b = pq.top(); pq.pop();
        cost += a + b;
        pq.push(a + b);                                      // the merged node may be the next smallest
    }
    return cost;
}

// Most frequent first, never the same as the last one (no two equal neighbours).
string noTwoAdjacent(const string& s) {
    int cnt[26] = {};
    for (char ch : s) cnt[ch - 'a']++;
    priority_queue<pair<int,char>> pq;                       // {count, letter}, biggest count on top
    for (int c = 0; c < 26; c++) if (cnt[c]) pq.push({cnt[c], char('a' + c)});
    string out;
    while (pq.size() >= 2) {
        auto [c1, x] = pq.top(); pq.pop();
        auto [c2, y] = pq.top(); pq.pop();
        out += x; out += y;                                  // two different letters per round
        if (--c1) pq.push({c1, x});
        if (--c2) pq.push({c2, y});
    }
    if (!pq.empty()) {
        if (pq.top().first > 1) return "";                   // one letter left over more than once
        out += pq.top().second;
    }
    return out;
}
Remember this · Greedy
Trigger
Maximise count / minimise cost with a natural "best next step", sorting makes the choice obvious, reachability.
Think
Is there a choice I can always make safely without looking ahead?
Key question
Why can't the greedy choice be worse than the optimal one? (Try to break it with a small example.)