DSA DSA In Depth
0%
design regret-greedypriority-queuescheduling

Regret Greedy (Undoable Greedy with Priority Queues): Scheduling with Deadlines & Stock Trading

7 min read Module 6.1: Recursion, Backtracking, Pruning & Greedy Exchange Arguments Updated Oct 23, 2026
📄 Cheat Sheet
Regret Greedy with Priority Queue Rollback (Course Schedule III — Moore-Hodgson Algorithm)
Algorithms Made Easy Regret Greedy with Priority Queue Rollback (Course Schedule III — Moore-Hodgson Algorithm) Watch on YouTube ↗

Demonstrates how maintaining a priority queue of past choices allows a greedy algorithm to 'change its mind' in O(log N) time without full DP.

0:00 / --:--

1. When Pure Greedy Fails: Retroactive Replacement via Heaps

Consider the Variable-Length Course Scheduling Problem (Moore–Hodgson Algorithm, 1968 / Course Schedule III): Given $N$ courses, each with duration $t_i$ and deadline $d_i$, maximize the number of courses completed. Unlike Unit-Time Scheduling ($t_i = 1$), variable durations $t_i$ do not form a matroid! If we sort by deadline $d_1 \le d_2 \le \dots \le d_n$ and greedily take course $1$ ($t_1 = 100, d_1 = 100$), we might block courses $2$ and $3$ ($t_2 = 30, d_2 = 110$ and $t_3 = 30, d_3 = 120$).

Core Invariant — Moore–Hodgson Regret Greedy Invariant

Process jobs in increasing order of deadline $d_1 \le d_2 \le \dots \le d_n$, maintaining a max-heap $H$ of durations of currently selected jobs and their total time $T = \sum_{j \in H} t_j$. For each job $i$, optimistically insert $t_i$ into $H$ and set $T \leftarrow T + t_i$. If $T > d_i$ (the schedule overflows $i$'s deadline), immediately evict the longest-duration job $t_{\max} = H.\text{pop()}$ and set $T \leftarrow T - t_{\max}$.
Why this is optimal: After processing prefix $1..i$, $H$ achieves the maximum possible job count $k$ AND simultaneously achieves the minimum possible total completion time $T$ among all valid $k$-job subsets of $1..i$.

2. Virtual Cancellation (Slope Trick / Stock Trading with Unlimited Transactions)

A second, deeper form of Regret Greedy is Virtual Undo (Equivalent to a 1-capacity Successive Shortest Path augmenting path or Slope Trick): Suppose on Day $i$ the stock price is $p_i$, and you want to pair Buy/Sell days to maximize profit (or match tasks to servers). If you buy at $p_1 = 10$, sell at $p_2 = 15$ (profit $+5$), and then see $p_3 = 22$, you regret selling at $15$ instead of $22$.

Observe the telescope identity:

$$(p_2 - p_1) + (p_3 - p_2) = p_3 - p_1$$

When we match $p_2$ with $p_1$ to book profit $p_2 - p_1$, if we push $p_2$ back into the min-heap as a "virtual buy option", then later buying that virtual $p_2$ and selling at $p_3$ adds $p_3 - p_2$, which algebraically cancels the intermediate sale at $p_2$ and converts the trade into $(p_1, p_3)$!

flowchart LR subgraph Step1["Day 1-2: p_1=10, p_2=15"] H1["Min-Heap: [10]"] -->|"p_2=15 > 10:
Profit += (15 - 10) = +5"| H2["Push 15 twice (1 real, 1 virtual undo)
Min-Heap: [15, 15]"] end subgraph Step2["Day 3: p_3=22"] H2 -->|"Pop virtual 15:
Profit += (22 - 15) = +7"| H3["Total Profit = (15-10) + (22-15)
= 22 - 10 = +12!"] end

Hardware Reality — $O(N \log N)$ Flat Binary Heap vs. $O(N \cdot D)$ Pseudo-Polynomial DP

Solving Variable-Length Deadline Scheduling via Dynamic Programming requires an $O(N \cdot \max d_i)$ table. When deadlines reach $d_i = 10^9$, the DP table requires $8 \text{ GB}$ per row and is completely intractable. Moore-Hodgson's Regret Greedy uses a single std::priority_queue<int> backed by a contiguous std::vector<int> of size $\le N$, solving $N = 10^6, d_i = 10^{18}$ in 45 milliseconds and 4 MB of L3 cache.

3. Complexity Matrix

Regret-Greedy Heap Rollback, Matroid Basis & CDCL SAT Implication Graph
O(N log N) Regret / 1-UIP CDCL

Trace Moore-Hodgson Regret-Greedy max-heap job eviction, weighted Matroid greedy basis construction, and CDCL SAT unit propagation + 1-UIP conflict clause learning.

Step 1 / 8
Custom Input:
Timeline

Invariant & State

Moore-Hodgson Regret Invariant: Accepting J2 pushes total time to 4+3=7 > d2=6. Pop max-duration job J1 (p=4) from heap to restore feasibility!
🌳 Live 2D Hierarchical Tree Topology — Regret-Greedy Heap Rollback, Matroid Basis & CDCL SAT Implication Graph Visited/Active Pivot/Root Resolved
Max p=4 J1 (p… [0] Total ti J2 (p… [1] Next J3 (p… [2]
[0]J1 (p=4, d=4)Max p=4 in Heap
[1]J2 (p=3, d=6)Total time=7 > d=6!
[2]J3 (p=2, d=7)Next
Before Eviction
Time = 7 > Deadline 6
Max-Heap Eviction
Drop J1 (p=4)
After Eviction
Time = 3 ≤ 6 (Feasible)
[Step 1] Deadline overflow at J2; max-heap evicts longest accepted job J1 (p=4), reducing total time from 7 to 3.
Problem Variant Dynamic Programming Regret Greedy (Heap) Heap Space Mechanism
Moore-Hodgson ($1 | d_j | \sum U_j$) O(N * max D) O(N log N) O(N) Evict max $t_j$ when $\sum t_j > d_i$
Farthest Building / Shielded Jumps ($K$ Ladders) O(N * K) O(N log K) O(K) Min-heap of $K$ largest climbs
Minimum Refueling Stops O(N^2) O(N log N) O(N) Max-heap of passed fuel stations
Buy/Sell Stock Multi-Hold (Slope Trick) O(N^2) O(N log N) O(N) Telescoping virtual-buy re-insertion

4. Reference C++20 Implementation

#include <algorithm>
#include <cstdint>
#include <functional>
#include <queue>
#include <vector>

struct Course {
    long long duration;
    long long deadline;
};

// 1. Moore-Hodgson Algorithm (Course Schedule III): Max courses completed before deadline
int max_courses_moore_hodgson(std::vector<Course> courses) {
    std::sort(courses.begin(), courses.end(), [](const Course& a, const Course& b) {
        return a.deadline < b.deadline;
    });

    std::priority_queue<long long> max_durations; // Max-heap of chosen course durations
    long long total_time = 0;

    for (const auto& c : courses) {
        total_time += c.duration;
        max_durations.push(c.duration);
        if (total_time > c.deadline) {
            // Regret Step: Evict the single longest course taken so far (might be c itself!)
            total_time -= max_durations.top();
            max_durations.pop();
        }
    }
    return static_cast<int>(max_durations.size());
}

// 2. Virtual Undo (Telescoping Heap): Buy/Sell at most 1 share per day, hold unlimited shares
long long max_profit_multi_share_trading(const std::vector<long long>& prices) {
    // Min-heap of available buy prices (both unused days and virtual undo options)
    std::priority_queue<long long, std::vector<long long>, std::greater<long long>> min_heap;
    long long total_profit = 0;

    for (long long p : prices) {
        if (!min_heap.empty() && min_heap.top() < p) {
            total_profit += (p - min_heap.top());
            min_heap.pop();
            // Push p TWICE:
            // 1st copy: allows cancelling the sale at p if a higher price p' > p appears later:
            //           (p - buy) + (p' - p) = p' - buy
            // 2nd copy: allows buying a brand new share on this day at price p
            min_heap.push(p);
        }
        min_heap.push(p);
    }
    return total_profit;
}

Production Pitfall — Assuming Regret Greedy Works When Both Weights AND Profits Vary

In Moore-Hodgson's algorithm, every job has unit profit ($1$ course = $+1$ count) and variable duration $t_i$, so evicting the maximum $t_j$ strictly dominates any other single-job eviction. If jobs have both arbitrary durations $t_i$ and arbitrary profits $v_i$, a single eviction in a priority queue is no longer sufficient because trading one large high-profit job for two small medium-profit jobs is the 0/1 Knapsack problem (NP-hard)!

Check yourself Question 1 of 4
Concept Check Moore-Hodgson Algorithm Eviction Rule recall

In the Moore-Hodgson algorithm (Course Schedule III) for maximizing the number of variable-duration jobs $(t_i, d_i)$ completed before their deadlines, we sort jobs by deadline $d_i$ ascending. When adding job $i$ causes total duration $T$ to exceed $d_i$, what action does the algorithm take?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

Why does the $O(N \log N)$ Moore-Hodgson priority-queue algorithm break down if each course $i$ has an arbitrary duration $t_i$, deadline $d_i$, AND arbitrary profit $v_i$ (where we want to maximize total profit $\sum v_i$)?

Key Takeaways

Regret Greedy records past greedy choices in a priority queue and retroactively replaces the worst choice in O(log N) time when a constraint is violated.
Moore-Hodgson's algorithm sorts variable-length jobs by deadline ascending, adds each job, and pops the longest job whenever total time exceeds the current deadline.
Virtual Undo (Slope Trick) pushes the matched price p back into a min-heap so a future price p' > p telescopes (p - b) + (p' - p) = p' - b.
Regret Greedy works when replacing 1 item for 1 item preserves cardinality while monotonically improving the resource slack.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue