Study Notes
Notesapplied

Greedy — A Masterclass in Thinking

A deep dive, not a cheat sheet — built to teach how to recognise when greedy is correct and invent the greedy choice yourself. A repeatable engine (propose candidates → break the losers with counterexamples → prove the survivor), the exchange & staying-ahead proofs, ten pattern families, and interactive widgets: an interval-scheduling timeline where you pick the sort key and watch greedy win or lose, an exchange-argument prover, and a jump-game reachability stepper.

30 min read·verified Content review 2026·reviewed 2026-08-30

After this you should be able to

  • Explain the core idea in Greedy — A Masterclass in Thinking
  • Apply Greedy — A Masterclass in Thinking in a representative exercise
Discovery EngineExchange ProofsInteractive
DSA · The Thinking Course

The greedy solution is almost always trivial — sort, then one pass. The hard part is everything the editorial skips: knowing greedy even applies, inventing the right choice, and trusting it won't betray you. This page teaches that missing middle: how experienced solvers manufacture a greedy rule and prove it, on a problem they've never seen.

For: Java solvers who find greedy "obvious only in hindsight" Style: first-principles · interactive Goal: solve unseen problems
By the end, greedy will stop feeling like a magic trick you either "see" or don't. You'll own a repeatable procedure for generating candidate greedy choices, killing the wrong ones with counterexamples, and proving the survivor safe with an exchange or staying-ahead argument. That procedure — not a bag of memorized rules — is the whole game.
i

How to actually use this page#

This is long on purpose — it's a course, not a cheat sheet (though the cheat sheets are at the bottom). Read it like this:

01

Fight the widgets#

The interval-timeline lets you choose the wrong sort key and watch greedy lose. Do that first — losing on purpose is how the correct choice earns its place.

02

Guess before revealing#

Every purple "pause & think" box is a real question. Commit to an answer — especially "can I break this greedy rule?" Reveals only help once you've tried.

03

Live in Parts III & IV#

The Discovery Engine and the proof toolkit are the reusable skill. Everything else is practice for them. Re-run them on every new problem forever.

ii

The map#

Front-loaded toward thinking & proving, back-loaded with patterns & reference. Jump around with the rail on the right (or the "Sections" button on mobile), but the first read should be in order.


Part I

Mindset#

Before any sorting: what greedy is, and why a five-line algorithm can be the hardest thing on the page. Fix the mental model here and the patterns later feel inevitable instead of arbitrary.

01 · What greedy actually is#

Forget "makes the locally optimal choice at each step" for a minute. That's the description of the code, not the reason it's allowed. Here's the feeling:

Greedy is a search that never backtracks — and it's correct exactly when there is always a first move you can make with zero regret. "Zero regret" means: some optimal solution agrees with that move, so committing to it can never cost you the best answer. You grab the safe move, throw away the rest of the problem's past, and repeat on what remains.

Everything else — the sort key, the heap, the one-pass loop — is just machinery for exposing which move is regret-free. Once you believe greedy is "commit only to provably-safe moves," the mystique inverts: the code was never the hard part. The hard part is the licence to commit. DP earns its answer by comparing every future; greedy earns its answer by proving it never needs to.

The two mental models, ranked by usefulness#

Model A · best
The no-regret move

At each step, is there an option that some optimal solution is guaranteed to contain? If yes, take it — you've lost nothing. This is how you'll actually justify solutions. Finding that move is the whole job.

Model B
The staying-ahead runner

Greedy is a runner who, after every step, is at least as far ahead as any other runner. If it never falls behind, it can't lose. Great for seeing why the sweep works.

Analogy · catching a train with no return ticket

You're crossing a city, hopping trains, and every ticket is one-way — you can never go back. Reckless hopping (grab whatever leaves first) sometimes strands you. But if you can prove that taking the earliest-arriving train never rules out any onward journey a smarter traveller could make, then hopping on it is free — you're always at least as well-placed as anyone. Greedy is committing to one-way tickets you've proven are safe.

Now we can earn the textbook words. Greedy is correct when a problem has both:

  • Greedy-choice property — there's always a locally-optimal move that is part of some globally-optimal solution. (This is "the no-regret move exists." It's the property you must prove, usually by an exchange argument.)
  • Optimal substructure — after committing to that move, what remains is a smaller instance of the same problem, and an optimal solution to the whole contains an optimal solution to the rest. (This is what lets you repeat.)

DP needs optimal substructure too — but DP lacks the greedy-choice property, so it must try every first move and compare. The single question that separates them: is there one first move I can commit to blindly, or must I compare futures? Commit → greedy. Compare → DP.

Pause & think: "make change with the fewest US coins (1, 5, 10, 25)" — greedy works. "Make change with coins {1, 3, 4}" — greedy breaks. Same algorithm. What changed? reveal

Only the coin set changed — and with it, whether the greedy-choice property holds. With {1,5,10,25} it's provable that grabbing the largest coin ≤ amount never blocks a better solution. With {1,3,4} it's false: for amount 6, grabbing the largest (4) forces 4+1+1 = 3 coins, but 3+3 = 2 coins is better. Greedy correctness is a property of the specific problem instance, not the strategy. That fragility — a tiny data change silently flips correct to wrong — is exactly why greedy needs a proof, and exactly what makes it scary. We'll make that fear productive.

02 · Why humans struggle with greedy#

You are not bad at greedy. You're missing the two things editorials delete: the search for the choice and the proof that it's safe. What's left — the sort and the loop — looks so simple that your own struggle feels like stupidity. It isn't. Let's name the specific gaps.

The code is short, so the difficulty is invisible#

A DP has a scary-looking table; you expect it to be hard. Greedy is sort(); for(...) if(safe) take(); — three lines. The hardness moved into an invisible place: proving those three lines are correct. You're blamed for finding a "trivial" problem hard, when the real difficulty was silently removed. Fix: we make the proof visible and central (Part IV).

Too many choices look plausible#

Sort by start? By end? By length? By value? By ratio? Each feels defensible, and the editorial just asserts one with no reason. So it reads as arbitrary magic. Fix: the engine generates all the candidates on purpose, then eliminates the wrong ones with counterexamples — so the survivor is earned, not guessed.

Greedy is fragile and gives no warning#

A wrong greedy often passes the sample cases and the small tests, then fails on case 47 with no hint why. There's no red squiggle for "your choice isn't safe." Fix: we install a reflex — never trust a greedy you haven't either proven or stress-tested against brute force (Part VII).

"How did they think to sort by that?"#

The finished solution sorts by finish time and it looks like it fell from the sky. It didn't: the author asked "which move is dominant?" and derived that finishing earliest leaves the most room. Fix: Part V teaches sorting as "reveal the dominant choice," not as a memorized incantation.

Confusing "greedy feels right" with "greedy is right"#

Intuition is a hypothesis generator, not a proof. Beginners stop at "this feels optimal." Experts treat that feeling as step one of five and immediately attack it. Fix: we drill the habit of trying to break your own choice before trusting it.

Reframe

"How did anyone think of choosing that first?" almost always has the same answer: they generated several candidate choices, tried to break each with a tiny example, and kept the one that survived — then proved it. You already know how to try small examples. So you're much closer than you feel — you're one disciplined procedure away.

Quick check
What is the genuinely hard, transferable skill in greedy problems?

Part II · recognition

Recognizing greedy#

Before you invent a choice, you need to suspect greedy in the first place — and, just as important, know when to reject it. Here's how to tell greedy apart from the algorithms it's most often confused with.

03 · When to suspect greedy — and when to run#

Greedy and DP answer the same kinds of questions (optimize / count / feasibility over choices), which is why they're confusable. The difference is never in the question — it's in whether a single first move is safe to commit to. Run this decision flow:

Q1 · Objective

Is the task an optimization ("max / min / most / fewest / earliest") or a feasibility ("can we / is it possible") over a set of choices?

▼ yes — both greedy & DP are on the table; keep going
Q2 · The commit test

Is there an obvious "best-looking" first move — a dominant option — such that you can argue no optimal solution is hurt by taking it?

Yes → try greedyCommit to that move, recurse on the rest. Now you owe a proof (Part IV) or a stress test (Part VII). Most "sort + one pass" solutions live here.
No → lean DPIf a locally-worse move can win globally (you can build a counterexample), you must compare futures. That's dynamic programming. Greedy is dead here.
▼ still unsure? use the tie-breakers below

The confusion table — greedy vs its look-alikes#

If the problem…Reach forTell / why not greedy
has a safe dominant first move; "sort then sweep" smells rightGreedyYou can commit without comparing futures.
lets a locally-worse choice win globally (you can build a counterexample)DPNo greedy-choice property; you must remember sub-answers & compare.
splits into independent halves that don't share subproblemsDivide & Conquere.g. merge sort. No "best local move" — you just split and combine.
asks shortest/cheapest path or level-order over a graphBFS / DijkstraDijkstra is greedy (safe: nearest unfinished node) + a heap. Plain BFS for unweighted.
explores all configurations, pruning dead ends, needs "all solutions"BacktrackingYou do revisit / undo choices — the opposite of greedy's no-backtrack.
optimizes over a contiguous window / running subarraySliding window / two pointersOften a specialized greedy (extend/shrink is the safe move); reach for these first when contiguity is central.
searches a monotonic answer-space ("smallest X that works")Binary search on answerThe feasibility check inside is sometimes greedy — but the outer loop is a search, not a sweep.
The strongest positive signals for greedy

① The answer is a schedule, ordering, or selection and "process in a sorted order" feels natural. ② There's a clearly dominant option (an interval that finishes first, the item with best value-per-weight, the two smallest things to merge). ③ Constraints are small in structure but large in n — greedy's O(n log n) fits where DP's states would explode. ④ The problem is a fractional or divisible version of an NP-hard one (fractional knapsack is greedy; 0/1 knapsack is DP).

The strongest warning signs — do NOT commit greedily

① You can construct a small case where the "obvious" move loses. ② The choice at step i depends on choices far in the future in a way you can't summarize. ③ The problem is a known DP (0/1 knapsack, coin change with arbitrary coins, longest increasing subsequence, edit distance). ④ You catch yourself saying "it probably works" without a proof or a stress test — that sentence is a bug report.

Pause & think: "Given activities with start/end times, select the maximum number that don't overlap." Greedy or DP? reveal

Greedy. There is a provably dominant first move: pick the activity that finishes earliest. Intuition: finishing earliest leaves the most room for everything after, and we'll prove (staying-ahead, Part IV) that no selection can beat it. You commit and recurse — never comparing futures. Contrast "select the maximum total value of non-overlapping activities with arbitrary values" → that flips to weighted interval scheduling, a DP, because a high-value later activity can beat two early cheap ones. The structure barely changed; the algorithm did. Feel how sensitive greedy is.


Part III · the core skill

The Discovery Engine#

The reusable procedure that turns "I have no idea what to sort by" into a proven greedy. This is the chapter to re-run on every new problem. We'll drive it end-to-end on Activity Selection, mistakes included.

04 · The engine: candidates → counterexamples → proof#

DP's engine is "recursion, then cache." Greedy's engine is different in spirit: propose, attack, prove. You are a prosecutor trying to convict a greedy rule — and only the one you fail to break gets to stand trial by proof.

1. Nail the objective (max/min/count/feasible).   2. Write the honest brute force so you know ground truth.   3. Brainstorm several candidate greedy choices (usually different sort keys).   4. Attack each with a tiny counterexample; delete the losers.   5. Take the survivor as your candidate.   6. Prove it safe (exchange / staying-ahead) — or stress-test at scale.   7. Implement: sort/heap + one pass.

A full worked derivation: Activity Selection#

Problem: you have n activities, each with a start and finish time. You can do one at a time; two activities conflict if they overlap. Select the maximum number of non-overlapping activities.

Step 1–2 · Objective & brute force#

Objective: maximize the count of chosen non-overlapping activities. Ground-truth brute force: try every subset, keep the largest that has no overlaps — O(2ⁿ). Useless for big n, but priceless as an oracle to test candidates against (Step 4, and again in debugging).

Step 3 · Brainstorm candidate choices#

What could the "obvious" first pick be? Don't commit — list them. Each is a hypothesis:

Candidate A
Earliest start

"Start as soon as possible." Intuitively productive — get going early.

Candidate B
Shortest duration

"Small activities waste the least time, so we fit more." Sounds efficient.

Candidate C
Earliest finish

"Free up the resource as soon as possible for whatever's next."

All three are reasonable. That's the trap — reasonableness is not correctness. Now we hunt counterexamples.

Step 4 · Attack each candidate — the interactive part#

Below is one fixed set of six activities. Pick a sort key and step the greedy sweep: it keeps an activity if it starts at/after the last kept finish, else rejects it. Watch two of the three "reasonable" rules select fewer than the optimum of 4. Losing on purpose is the point.

Activity Selection · 6 activities · greedy sweep
Sort by:
being considered kept rejected (overlaps)
Earliest start failsThe activity that starts first here is a long one [0,7]. Grabbing it burns the whole timeline and blocks everything → only 2 selected. Starting early is not the same as finishing early.
Shortest first failsA short activity can sit in the middle and clip two others that could both have been chosen. Shortest-first gets 3, missing the optimum of 4.

Earliest finish (C) is the lone survivor — it hits 4. The counterexamples didn't just crown it; they told us why: what matters isn't when an activity starts or how long it is, but how soon it frees the resource. Finishing early is the property that never wastes the future.

Pause & think: surviving three counterexamples proves earliest-finish is correct… right? reveal

No — and this is the most important habit on the page. Passing a few examples means a rule isn't obviously wrong; it does not mean it's right. A greedy that fails only on the 500th case looks identical to a correct one until case 500. Counterexamples can kill a rule (one failure = dead) but can never certify one. To certify, you need either a real proof (Step 6, next) or a stress test over thousands of random inputs (Part VII). Surviving Step 4 earns a rule the right to trial by proof — nothing more.

Step 5–6 · Take the survivor & prove it (staying-ahead)#

Candidate: repeatedly pick the compatible activity with the earliest finish time. Here's the proof, intuitively, then we'll formalize it in Part IV. Number greedy's picks g₁, g₂, … by finish time, and any other solution's picks o₁, o₂, … likewise.

Greedy never falls behind#

Greedy's first pick finishes no later than anyone's first pick (it chose the global earliest finish). Claim: greedy's k-th pick always finishes no later than any solution's k-th. It stays ahead.

Being ahead is never a disadvantage#

Because greedy's k-th activity frees the resource earliest, every activity available to a competitor after their k-th is also available to greedy after its k-th. Greedy can match any move the competitor makes next.

So greedy can't be shorter#

If some solution had more activities than greedy, there'd be an activity it fits after position k that greedy couldn't — impossible, since greedy finishes earlier and has more room. Contradiction. Greedy's count is maximal. Proven.

Step 7 · Implement — five lines, now trusted#

// Activity Selection: max non-overlapping activities.
// Each row: {start, end}. Half-open intervals: [s, e).
int maxActivities(int[][] acts) {
    // The whole strategy: process by earliest finish time.
    Arrays.sort(acts, (a, b) -> Integer.compare(a[1], b[1]));  // sort by END
    int count = 0, lastEnd = Integer.MIN_VALUE;
    for (int[] a : acts) {
        if (a[0] >= lastEnd) {   // compatible: starts at/after last finish
            count++;             // COMMIT — no regret (proven safe)
            lastEnd = a[1];      // resource now free at a[1]
        }
    }
    return count;
}
What just happened

We never "guessed" that we should sort by finish time. We generated three candidates, broke two with tiny counterexamples, and proved the third with a staying-ahead argument. The sort key is the output of the engine, not a fact to memorize. Run this same propose→attack→prove loop on any greedy and it will hand you the sort key and the confidence to trust it.

Quick check
Your candidate greedy passes all 5 examples you tried. What have you established?

05 · Inventing the choice — the questions that surface it#

Step 3 ("brainstorm candidates") feels like the creative leap, but it's mostly mechanical if you interrogate the problem with the right questions. Ask these, in order, out loud:

"What single decision must I commit to first?"#

Name the atomic choice: which interval to keep, which item to take, which two things to merge. Greedy always reduces to one repeated decision — find it.

"Is one option dominant — never worse than another?"#

A move is dominant if, for any solution using a different move, you could swap this one in without losing. Dominance is the greedy choice. (E.g. an activity that finishes earliest dominates one that finishes later — it leaves a superset of future options.)

"Can sorting expose the dominant move?"#

If "best" means "extreme in some quantity" (earliest, largest, cheapest, best ratio), sorting by that quantity puts the dominant move first at every step. This is why ~80% of greedy solutions open with a sort.

"What can I safely forget after committing?"#

Greedy works only if the past collapses to a tiny summary (a lastEnd, a running total, a heap of leftovers). If you'd need to remember the whole history to make the next choice, greedy is probably wrong — that's a DP state.

"Run the exchange test in my head."#

Take my proposed first move X. Imagine an optimal solution that does something else. Can I rewrite it to use X without making it worse? If yes → X is safe. If I get stuck → likely not safe; hunt a counterexample.

The dominance lens — the single most useful idea in this section

Reframe every greedy as: "Which option's set of future possibilities contains every other option's?" That option is dominant, and taking it can never cost you. Activity selection: earliest-finish dominates because its remaining-time window is a superset of any later-finishing choice's. Fractional knapsack: highest value/weight dominates because each unit of capacity spent buys the most value. Connect-ropes / Huffman: merging the two smallest dominates because they'll be re-added the most times, so you want them merged first. Find the superset, and the sort key names itself.

Invent-the-choice drill

For each, name the dominant first move and the sort key. Reveal to check the reasoning, not just the answer.

Minimum number of arrows to burst all balloons (each balloon is an interval; an arrow at x pops every interval covering x)

Dominant move: shoot an arrow at the earliest finishing balloon's right edge — it pops that balloon and every other whose interval also covers that x, for free. Sort by end; sweep, firing a new arrow only when a balloon starts after the current arrow's x. It's activity-selection's twin: "earliest finish frees the future."

Fractional knapsack: capacity W, items with (value, weight), you may take fractions

Dominant move: take as much as possible of the item with the highest value/weight ratio — every unit of capacity it consumes buys more value than any other item's unit. Sort by ratio desc; fill greedily, splitting the last item. (The fractional permission is what makes it greedy; 0/1 forbids splitting and becomes DP.)

Connect n ropes into one; cost of joining two ropes = sum of their lengths; minimize total cost

Dominant move: always join the two shortest ropes available. Why: a rope's length is paid once per future join it participates in, so short ropes should be combined early (they'll be re-summed the most times). Not a one-time sort — the "two smallest" change after each merge, so use a min-heap. This is Huffman coding.


Part IV · the crown

Proving greedy works#

This is the chapter that separates people who "know some greedy problems" from people who can trust a new greedy on the spot. Two proof techniques cover the vast majority of greedy problems. Learn them as moves, not definitions.

06 · The proof toolkit#

Exchange argument — take any optimal solution, show you can transform it step-by-step into greedy's solution without ever making it worse. If OPT can become greedy for free, greedy is optimal.  Staying-ahead — show greedy's partial solution is, after every step, at least as good as any other's by some measure. If it never falls behind, it can't lose. Everything else (cut property, matroids) is a named special case of these.

Technique 1 · The exchange argument, visualized#

This is the workhorse. The move: assume an optimal solution OPT disagrees with greedy at the first choice. Swap greedy's choice into OPT. Show the swap keeps OPT valid and no worse. Since one swap removes one disagreement without cost, repeating turns OPT into greedy — so greedy is optimal too. Step through it on activity selection:

Exchange argument · activity selection

The engine of the whole proof is one line: greedy's choice finishes no later than OPT's, so wherever OPT's choice fit, greedy's fits too — the swap can't create a conflict, and it can't reduce the count. That "no later, so it fits anywhere yours did" reasoning is the shape of nearly every greedy exchange proof. Memorize the shape, not the problem.

Exchange · recipe
The four beats

1. Assume OPT ≠ greedy; find the first difference. 2. Swap greedy's choice into OPT there. 3. Prove the result is still valid (no conflict / constraint broken). 4. Prove it's no worse (same or better objective). Conclude by induction.

Exchange · when
Best for

Selection & ordering problems where "swap this element for that one" is a clean operation: activity selection, scheduling to minimize lateness (earliest-deadline-first), Huffman, minimum spanning tree edge choices.

Pause & think: in the exchange, why must we pick the first place OPT and greedy differ, not any random difference? reveal

Because up to the first difference, OPT and greedy made the same choices, so they're in the identical situation right before it — which is exactly what lets us argue greedy's alternative is a legal, no-worse swap in OPT's context. Swapping at a later, arbitrary difference risks interacting with earlier divergences you haven't accounted for. "First difference" keeps the induction clean: each swap strictly increases the agreement prefix by one, so after finitely many swaps OPT becomes greedy.

Technique 2 · Staying-ahead ("greedy is never behind")#

Instead of transforming OPT, you race against it. Define a measure of "how well positioned" a partial solution is after k steps, then prove by induction that greedy's measure is always at least as good as any other's. If greedy is never behind at any step, it can't finish worse.

Pick the right "ahead" measure#

Activity selection: "finish time of the k-th chosen activity" (smaller = more ahead). Coin/interval covering: "how much of the target is covered after k picks" (more = ahead).

Base + inductive step#

Base: after 1 pick greedy is ahead (it chose the extreme). Step: if greedy is ahead after k, its (k+1)-th pick keeps it ahead, because its lead only widens the set of moves available to it.

Conclude#

Since greedy is never behind, any solution that beat it would need a step where it pulled ahead — impossible by the induction. So greedy matches the optimum.

Exchange vs staying-ahead — which to reach for

They often both work; pick by ergonomics. Staying-ahead shines when there's a clean scalar that measures progress and greedy clearly extremizes it each step (activity selection, Dijkstra, min-arrows). Exchange shines when "swap element X for Y" is a tidy, obviously-legal operation (Huffman, MST, scheduling for min lateness, most "sort by a ratio" proofs). If one feels like it's fighting you, try the other.

Special case · the cut property (why MST greedies work)#

For Minimum Spanning Tree (Kruskal / Prim), the greedy-choice property has a famous name: the cut property.

For any cut (any split of the vertices into two groups), the minimum-weight edge crossing that cut is safe — it belongs to some MST. Proof is a one-line exchange: if an MST avoided that edge, adding it makes a cycle that crosses the cut at least twice; swap the min-weight crossing edge in for a heavier crossing edge on the cycle — the tree stays spanning and gets no heavier. So the min crossing edge is always includable.

Kruskal applies this by always adding the globally-cheapest edge that doesn't form a cycle (it's the min crossing edge for the cut separating the two components it joins). Prim applies it by always adding the cheapest edge leaving the tree built so far. Same cut property, two orderings.

The deep "why" · matroids (optional, but it demystifies everything)#

You don't need this to solve problems, but it answers "why does sort-and-take work for MST and scheduling but not knapsack?" A matroid is a structure with an "exchange property": if you have two independent sets and one is bigger, you can always move an element from the bigger into the smaller and stay independent. Whenever your problem's feasible solutions form a matroid, the greedy "sort by weight, take if still feasible" is provably optimal. MST's forests form a matroid → Kruskal works. Knapsack's under-capacity sets do not form a matroid → greedy fails, DP needed. The exchange property of matroids is literally the exchange argument, generalized.

Quick check
"Assume OPT differs from greedy; at the first difference, replace OPT's choice with greedy's and show OPT stays valid and no worse." Which proof is this?

07 · Why greedy fails — and how to catch it early#

Knowing where greedy breaks is as valuable as knowing where it works — it's what stops you from confidently shipping a wrong five-liner. Every greedy failure has the same signature: a locally-worse choice can win globally. Learn to smell it.

Coin change {1,3,4}, amount 6Greedy grabs biggest ≤ 6 → 4, then 1+1 = 3 coins. Optimal: 3+3 = 2 coins. The locally-best coin (4) blocks the globally-best pairing. → DP.
Coin change {1,5,10,25}Here greedy is provably optimal (the US/"canonical" coin systems have the greedy-choice property). Same algorithm, different data — correctness hinges on the coin set, not the code.
0/1 KnapsackSort by value/weight and take greedily → can be arbitrarily bad. Capacity 10, items (v=60,w=10) vs (v=50,w=5)+(v=50,w=5). Greedy by ratio might grab the single 60; optimal takes the two fives for 100. Indivisibility breaks dominance. → DP.
Fractional KnapsackAllow fractions and greedy-by-ratio is optimal — you can always top off the last unit of capacity with the best remaining ratio. Divisibility restores dominance.
Longest path in a graph"Always walk to the nearest/farthest neighbor" fails wildly — a short first edge can strand you from a huge subtree. No dominant local move exists. → search / DP on DAGs.
Shortest path (non-negative)Dijkstra's greedy — "finalize the nearest unsettled node" — is provably safe (a shorter route to it would've been found first). Add negative edges and even this breaks → Bellman-Ford.
The instant you can build a small case where taking the locally-best option forces a globally-worse result, greedy is dead and you should reach for DP (compare futures) or a search. So: before trusting any greedy, spend 60 seconds actively trying to construct that case. If you can, you just saved yourself from a wrong submission. If you genuinely can't after real effort, proceed to a proof or stress test.
Pause & think: you've invented a greedy and can't find a counterexample in 5 minutes. Ship it? reveal

Not yet — but you're allowed to be optimistic now. "Can't break it quickly" raises your prior that it's correct, but greedy's fragility means the counterexample might just be bigger than your hand-examples reach. Two safe ways forward: (1) find a real proof (exchange / staying-ahead) — best, and often quick once you try; or (2) write a brute-force oracle and stress-test against it on thousands of random small inputs (Part VII). In an interview, verbalize the proof sketch; in a contest, run the stress test. "It felt right and passed the samples" is how wrong greedies reach production.


Part V · mechanics & tools

Sorting & data structures#

Greedy has two recurring engines: a sort that reveals the dominant move once, and a data structure that re-reveals it as the problem changes. Knowing which to reach for is half the implementation.

08 · Sorting — why greedy almost always starts here#

Sorting is greedy's default opening because it does one magical thing: it puts the dominant choice first at every step, so a single left-to-right pass can commit to moves in the right order. If your greedy choice is "the extreme one" (earliest, cheapest, best ratio), sorting by that key means "the front of the list is always the move to consider next."

Sort byUse when the dominant move is…Canonical problems
End / finish time"free the resource earliest"Activity selection, min arrows, non-overlapping intervals
Start time"process in timeline order" (then sweep/heap)Merge intervals, meeting rooms II, insert interval
Value / weight ratio"most value per unit of a budget"Fractional knapsack, cost-efficiency problems
Value / profit desc"grab the biggest prize first, fit it if you can"Job sequencing with deadlines
Deadline"do the most urgent first"Scheduling to minimize max lateness (EDF)
A custom pairwise rule"a & b should be adjacent in this order"Largest Number (compare a+b vs b+a), min-cost concatenations
The custom-comparator superpower

Some greedies sort by a rule that isn't a simple field — it's a pairwise "should a come before b?" For "arrange numbers to form the largest concatenation," you sort strings by (a+b) vs (b+a): put a first iff a+b > b+a. The proof this is even a valid ordering (transitive) plus an exchange argument (adjacent swaps never improve) is the whole solution. When "which comes first?" depends on the pair, reach for a comparator, and be ready to justify it's a total order.

Pause & think: activity selection sorts by end, but merge-intervals sorts by start. Both are interval problems — why different keys? reveal

Because the dominant move differs. In activity selection the move is "keep the one that frees the resource soonest" → earliest finish dominates. In merge intervals the goal is to fuse overlaps by scanning the timeline in order, so you need intervals in the order they begin → earliest start. The sort key is dictated by the dominance argument, not by "it's an interval problem." Always derive the key from what move is safe, never from the problem's surface category.

09 · Data structures — re-revealing the best move#

A one-time sort works when the "best move" order is fixed up front. But sometimes the best move changes as you commit (the two smallest ropes change after each merge; the nearest node changes as you settle others). Then you need a structure that re-surfaces the current best in O(log n).

Priority Queue / Heap
"Best move keeps changing"

When the dominant element is dynamic — smallest/largest right now, after prior commits. Huffman (two smallest), Dijkstra (nearest node), task scheduler, meeting rooms II (earliest-freeing room), merge k lists, "maximize by repeatedly taking the current max".

TreeMap / TreeSet
"Find the nearest available"

When you greedily need the closest value ≥ or ≤ something (floor/ceiling), or a sorted multiset you insert into and query. Course Schedule III, calendar/booking overlaps, "smallest slot that still fits."

Union-Find (DSU)
"Merge groups, avoid cycles"

The backbone of Kruskal's MST (add cheapest edge unless it cycles) and job-sequencing slot allocation ("find the latest free slot ≤ deadline" via DSU-on-slots).

Stack / Deque (monotonic)
"Greedily keep a best prefix"

Monotonic-stack greedies build an optimal sequence by popping dominated elements. Remove K Digits (pop bigger earlier digits), Remove Duplicate Letters (smallest lexicographic result), sliding-window max (deque).

Recognition shortcut

If the greedy choice is a fixed extreme known before you start → sort once. If the extreme is recomputed after each commit → heap. If you need "nearest available ≥/≤ x" repeatedly → TreeMap/TreeSet. If you're merging sets and rejecting cycles → union-find. If you're building an optimal subsequence by discarding dominated earlier picks → monotonic stack. The structure is dictated by how the best move evolves.


Part VI · pattern library

The pattern library#

Recognition shortcuts layered on top of the engine — never a replacement for it. For each family: the clue that flags it, the greedy choice, why it's safe, the trap, and a problem ladder. When you meet a new problem, ask "which family do the clues match?" to get a starting choice — then run the engine to confirm.

10 · The families#

Family 01 · the flagship

Interval scheduling — "earliest finish"#

sort by end · sweep keeping start ≥ lastEnd

Intuition: pick the interval that frees the resource soonest; it dominates because its remaining window is a superset of any later-finishing choice. Proof: staying-ahead / exchange (Part IV).

▹ "maximum non-overlapping", "minimum removals to make non-overlapping", "fewest arrows/points to stab all intervals"
▹ objective counts intervals (unweighted); resource is reusable once freed
Template
Arrays.sort(iv, (a, b) -> Integer.compare(a[1], b[1])); // by END
int taken = 0, lastEnd = Integer.MIN_VALUE;
for (int[] x : iv)
    if (x[0] >= lastEnd) { taken++; lastEnd = x[1]; }   // keep & advance
// "min removals" = n - taken. "min arrows" = same count, using x[0] > lastEnd (strict).
Common mistake

Sorting by start (the earliest-start trap) or by length. Both feel right and both lose — as the widget above shows. Also: mixing up open vs closed endpoints (does touching count as overlap?) flips a >= to >.

Problem ladder
  1. Non-overlapping Intervals medActivity selection in disguise: min removals = n − maxKept.
  2. Minimum Arrows to Burst Balloons medSame sweep; fire an arrow at each earliest end. Watch the strict/inclusive edge.
  3. Maximum Length of Pair Chain medLiteral activity selection; cements the "by end" reflex.
Family 02

Interval merging & the sweep line — "by start"#

sort by start · fold overlaps / count concurrent events

Intuition: to fuse or count overlaps, walk the timeline in the order things begin. For "how many resources at once?", turn each interval into a +1 at start and −1 at end, sort events, and track the running max — the classic min-platforms / meeting-rooms-II sweep.

▹ "merge overlapping", "insert interval", "minimum meeting rooms / platforms"
▹ answer is a set of fused intervals, or a peak count of simultaneous things
Template · min rooms via heap
Arrays.sort(iv, (a, b) -> Integer.compare(a[0], b[0]));   // by START
PriorityQueue<Integer> endTimes = new PriorityQueue<>();   // min-heap of end times
for (int[] m : iv) {
    if (!endTimes.isEmpty() && endTimes.peek() <= m[0]) endTimes.poll();          // a room freed up; reuse it
    endTimes.add(m[1]);           // occupy a room until m[1]
}
return endTimes.size();           // peak concurrency = rooms needed
Common mistake

For the event-sweep variant: ordering a start-event before an end-event at the same timestamp (or vice-versa) changes whether "touching" needs a new room. Decide the tie rule deliberately.

Problem ladder
  1. Merge Intervals medThe base fold; sort by start, extend or emit.
  2. Meeting Rooms II medHeap of end times, or the ±1 event sweep. Min-platforms twin.
  3. Car Pooling medDifference-array sweep; capacity feasibility.
Family 03

Reachability & jumps — "track the farthest"#

one pass · keep the max reach so far

Intuition: you don't need to know which path; you need the frontier. Maintain the farthest index reachable; if the current index is within it, extend the frontier. Safe because reaching farther is never worse. Step it below:

Jump Game · can we reach the last cell?
current index within reach unreachable
▹ "can you reach the end", "minimum jumps to reach the end", array values = jump range
▹ you care about whether/how-far you can get, not the exact route
Template · Jump Game (feasibility)
int farthest = 0;
for (int i = 0; i < nums.length; i++) {
    if (i> farthest) return false;               // fell off the frontier
    farthest = Math.max(farthest, i + nums[i]);   // extend reach
}
return true;
Common mistake

For minimum jumps (Jump Game II), don't increment a counter per index — advance in "levels": extend the current jump's range, and only when you reach its end do you spend a jump and adopt the new farthest. That's a BFS-flavored greedy.

Problem ladder
  1. Jump Game medFeasibility via farthest reach — the template above.
  2. Jump Game II medMinimize jumps; the "level boundary" greedy.
  3. Gas Station medCircular reachability; running tank + restart rule (next family).
Family 04

Running-total greedy — "reset when it can't help"#

one pass · a running sum + a smart restart

Intuition: carry a running quantity; when it goes negative (or otherwise can't contribute to any future optimum), drop it and restart — a prefix that only hurts is safe to abandon. This is the greedy heart of Gas Station and Kadane's max-subarray.

▹ "find the starting point so you never run out", "max subarray sum", circular tank/battery
▹ a partial accumulation that becomes a liability can be discarded without regret
Template · Gas Station
int total = 0, tank = 0, start = 0;
for (int i = 0; i < n; i++) {
    int gain = gas[i] - cost[i];
    total += gain;
    tank  += gain;
    if (tank < 0) { start = i + 1; tank = 0; }  // this prefix can't be a valid start
}
return total >= 0 ? start : -1;                 // solvable iff total gain >= 0
Why the restart is safe

If the tank goes negative at i, then no index between the old start and i can be a valid start (each would run dry even sooner). So skipping all of them to i+1 loses nothing — a clean exchange/staying-ahead argument.

Problem ladder
  1. Maximum Subarray (Kadane) medDrop a negative running prefix; the cleanest reset-greedy.
  2. Gas Station medCircular tank with the restart rule above.
Family 05

Two-pass greedy — "satisfy each side separately"#

left→right, then right→left, combine

Intuition: when each element must satisfy constraints from both neighbors, one pass can't see both directions. So make two: a left-to-right pass enforcing the left constraint, a right-to-left pass enforcing the right, and combine (usually max). Each pass greedily gives the minimum that satisfies one side.

▹ "each child gets more candy than a lower-rated neighbor", trapping-rain-water-style both-sides constraints
▹ local rule references both left and right; symmetric structure
Template · Candy
int[] c = new int[n];
Arrays.fill(c, 1);
for (int i = 1; i < n; i++)                    // left→right: respect left neighbor
    if (rating[i] > rating[i-1]) c[i] = c[i-1] + 1;
for (int i = n - 2; i >= 0; i--)               // right→left: respect right neighbor
    if (rating[i] > rating[i+1]) c[i] = Math.max(c[i], c[i+1] + 1);
int total = 0; for (int x : c) total += x;
return total;
Common mistake

Forgetting the Math.max in the second pass — you must keep whatever the first pass already guaranteed, not overwrite it. Overwriting silently breaks the left-side constraint.

Problem ladder
  1. Candy hardThe archetype; two passes + max-combine.
  2. Trapping Rain Water hardBoth-sides max; do it two-pass, then two-pointer to internalize the greedy.
Family 06

Heap-driven greedy — "the best move keeps changing"#

min/max-heap · repeatedly take & re-insert the current extreme

Intuition: when the dominant element is re-computed after each commit, a one-time sort can't keep up — a heap re-surfaces the current best in O(log n). Huffman merges the two smallest repeatedly; task scheduler always runs the most-frequent still-available task.

▹ "repeatedly combine the two smallest", "always process the largest remaining", "minimize cost of merges"
▹ after each step the pool of candidates changes, so their order must be re-derived
Template · Huffman / Connect Ropes
PriorityQueue<Integer> pq = new PriorityQueue<>();   // min-heap
for (int len : ropes) pq.add(len);
int cost = 0;
while (pq.size() > 1) {
    int a = pq.poll(), b = pq.poll();  // the two smallest — the safe merge
    cost += a + b;
    pq.add(a + b);                     // re-insert the merged rope
}
return cost;
Why two-smallest is safe (exchange)

The two least-frequent/shortest items are combined the most times across all future merges, so placing them deepest (merged earliest) minimizes total weighted cost. Swapping any other pair to the bottom can only raise the cost — a textbook exchange argument.

Problem ladder
  1. Minimum Cost to Connect Sticks medLiteral Huffman merge cost.
  2. Task Scheduler medGreedily run the most-frequent available task; heap or math.
  3. IPO hardTwo structures: unlock by capital (sort), then max-heap the profits.
Family 07

Monotonic-stack greedy — "build the best subsequence"#

stack · pop dominated earlier picks

Intuition: to build the lexicographically-best (smallest/largest) result, keep a stack and pop earlier elements that a current, better element dominates — as long as you can still afford to remove them. Each pop is a no-regret improvement.

▹ "smallest/largest number after removing k digits", "lexicographically smallest result", "remove duplicates keeping order optimal"
▹ answer is a subsequence chosen to be extremal; a later element can improve on an earlier one
Template · Remove K Digits (smallest result)
Deque<Character> st = new ArrayDeque<>();
for (char d : num.toCharArray()) {
    while (k > 0 && !st.isEmpty() && st.peek() > d) {
        st.pop(); k--;             // a bigger earlier digit hurts — drop it
    }
    st.push(d);
}
while (k-- > 0) st.pop();           // still must remove? drop from the end
// build result from the stack bottom-up, strip leading zeros
Common mistake

Forgetting the leftover-k and leading-zero cleanup, or popping when equal (you should only pop strictly bigger to stay stable). The "can I still afford it?" budget (k) is what makes each pop safe.

Problem ladder
  1. Remove K Digits medThe archetype monotonic greedy.
  2. Remove Duplicate Letters hardSame idea + a "last occurrence" affordability check.
Family 08

Deadline / profit scheduling — "grab the biggest prize that still fits"#

sort by profit desc · place in the latest free slot ≤ deadline

Intuition: take jobs from most to least profitable; schedule each as late as its deadline allows, to leave early slots open for other jobs. "Latest free slot ≤ deadline" is found fast with union-find over slots.

▹ "each job takes 1 unit, has a deadline & profit, maximize profit", "minimize maximum lateness"
▹ jobs compete for a limited set of time slots
Template · Job Sequencing
Arrays.sort(jobs, (a, b) -> Integer.compare(b.profit, a.profit)); // profit desc
boolean[] slot = new boolean[maxDeadline + 1];
int profit = 0;
for (Job j : jobs)
    for (int t = j.deadline; t >= 1; t--)   // latest free slot (DSU makes this O(α))
if (!slot[t]) { slot[t] = true; profit += j.profit; break; }
return profit;
Note

For minimize maximum lateness, the winning greedy is earliest-deadline-first instead — a clean exchange proof: any inversion (a later-deadline job before an earlier one) can be swapped to reduce max lateness. Different objective, different key.

Problem ladder
  1. Job Sequencing Problem medProfit-desc + latest-slot placement.
  2. Course Schedule III hardSort by deadline, max-heap of durations, drop the longest when over budget — a gorgeous heap greedy.
Family 09

Ratio greedy — "most value per unit of budget"#

sort by value/weight desc · fill, splitting the last

Intuition: when a budget is divisible, every unit should buy the best available rate. Sort by value/weight, take fully while it fits, then take a fraction of the next to top off. Divisibility is what makes it greedy — remove it (0/1) and you need DP.

▹ "maximize value under a capacity, fractions allowed", "best rate per unit"
▹ the resource is continuous / splittable
Template
Arrays.sort(items, (a, b) -> Double.compare((double) b.value / b.weight, (double) a.value / a.weight)); // ratio desc
double value = 0; int cap = W;
for (Item it : items) {
    if (cap == 0) break;
    int take = Math.min(it.weight, cap);          // full item, or a fraction
    value += (double) it.value * take / it.weight;
    cap   -= take;
}
return value;
The trap

Applying this to 0/1 knapsack. Without fractions, a great-ratio-but-large item can crowd out two items that together beat it — greedy is provably wrong. The word "fraction/divisible" in the statement is the whole license.

Problem ladder
  1. Fractional Knapsack medThe ratio greedy in its purest form.
  2. Maximum Units on a Truck easyDiscrete ratio greedy that happens to work (unit boxes) — contrast with 0/1.
Family 10

Minimum Spanning Tree — the cut property in code#

Kruskal: sort edges, add if no cycle (DSU) · Prim: grow from a node with a heap

Intuition: the cheapest edge crossing any cut is safe (Part IV). Kruskal sorts all edges and adds the cheapest that doesn't form a cycle; Prim grows a tree, always adding the cheapest edge leaving it. Both are the cut property applied in a different order.

▹ "connect all nodes at minimum total edge weight", "minimum cost to link everything"
▹ weighted undirected graph; want a spanning tree, not shortest paths
Template · Kruskal
Arrays.sort(edges, (a, b) -> Integer.compare(a.w, b.w)); // cheapest first
DSU dsu = new DSU(n);
int cost = 0, used = 0;
for (Edge e : edges) {
    if (dsu.union(e.u, e.v)) {   // union returns false if it would form a cycle
        cost += e.w;
        if (++used == n - 1) break;   // spanning tree complete
    }
}
return cost;
Note

Don't confuse MST with shortest paths: MST minimizes total tree weight; Dijkstra minimizes distance from a source. Both are greedy, but the safe move differs (cheapest crossing edge vs nearest unsettled node).

Problem ladder
  1. Min Cost to Connect All Points medMST on a complete graph; Kruskal or Prim.
  2. Network Delay Time medDijkstra — greedy shortest path, to feel the contrast with MST.
When you meet a new problem, don't ask "what's the trick?" Ask "which family do the clues match?" That hands you a candidate choice and sort key. Then run the Discovery Engine — try to break it, then prove it — before you trust it. Recognition (family) gives the hypothesis; the engine gives the confidence. Never skip the second half: greedy that "matched a pattern" but was never verified is exactly how wrong greedies get shipped.

Part VII · reference & practice

Reference & practice#

Copy-paste templates, the stress-test that stands in for a proof, the interview monologue, a sequenced roadmap, and one-page cheat sheets to revisit forever.

11 · Java templates#

The skeletons every greedy reuses. Fill in the comparator and the "is this move safe here?" check — the structure never changes.

Custom comparators & sorting objects#

// Sort intervals by end time (activity selection):
Arrays.sort(iv, (a, b) -> Integer.compare(a[1], b[1]));

// Multi-key: by end asc, tie-break by start desc:
Arrays.sort(iv, (a, b) -> a[1] != b[1] ? a[1] - b[1] : b[0] - a[0]);
// NOTE: use Integer.compare, not a-b, when values can overflow int subtraction.

// Pairwise "which should come first" (Largest Number):
String[] s = ...;
Arrays.sort(s, (x, y) -> (y + x).compareTo(x + y));  // x before y iff x+y is larger

// Objects by a derived ratio (fractional knapsack), descending:
items.sort((a, b) -> Double.compare((double) b.value / b.weight, (double) a.value / a.weight));

Priority queue (heap) — dynamic best move#

PriorityQueue<Integer> min = new PriorityQueue<>();               // min-heap
PriorityQueue<Integer> max = new PriorityQueue<>(Collections.reverseOrder());

// heap of objects by a field (e.g. by end time):
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);

// idiom: repeatedly take the extreme, transform, re-insert
while (pq.size() > 1) {
    int a = min.poll(), b = min.poll();
    min.add(a + b);   // Huffman-style merge
}

TreeMap / TreeSet — nearest available#

TreeSet<Integer> set = new TreeSet<>();
set.add(x);
Integer ge = set.ceiling(k);   // smallest element >= k  (nearest available slot up)
Integer le = set.floor(k);     // largest element  <= k  (nearest available slot down)

TreeMap<Integer,Integer> map = new TreeMap<>();   // sorted multiset / booking counts
map.merge(start, 1, Integer::sum);
map.merge(end, -1, Integer::sum);                // ±1 sweep; iterate keys in order

Union-Find (DSU) — merge sets, reject cycles#

class DSU {
    int[] p, r;
    DSU(int n) { p = new int[n]; r = new int[n]; for (int i = 0; i < n; i++) p[i] = i; }
    int find(int x) { return p[x] == x ? x : (p[x] = find(p[x])); }   // path compression
    boolean union(int a, int b) {
        int ra = find(a), rb = find(b);
        if (ra == rb) return false;               // already connected → would cycle
        if (r[ra] < r[rb]) { int t = ra; ra = rb; rb = t; }
        p[rb] = ra; if (r[ra] == r[rb]) r[ra]++;  // union by rank
        return true;
    }
}

Interval sweep (event sorting) — peak concurrency#

// Turn each interval into two events; sort; track a running counter's max.
int[][] ev = new int[2 * n][2];
for (int i = 0; i < n; i++) { ev[2*i] = new int[]{iv[i][0], +1}; ev[2*i+1] = new int[]{iv[i][1], -1}; }
// tie rule: process -1 (end) before +1 (start) at equal time if touching is allowed to share
Arrays.sort(ev, (a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
int cur = 0, peak = 0;
for (int[] e : ev) { cur += e[1]; peak = Math.max(peak, cur); }
return peak;
Template discipline

Every template has the same two holes: the ordering (comparator / heap) and the safe-move test (the if that decides to commit). That's the Discovery Engine crystallized: the ordering exposes the dominant move; the test is the proof, executed. Fill those two holes and the rest is boilerplate.

12 · Debugging greedy — the stress test is your proof substitute#

Greedy bugs are silent: you get a plausible number that's just wrong on some input you didn't try. You can't "read a table" like DP. Your single most powerful tool is the stress test: pit your greedy against a brute-force oracle on thousands of random small inputs. It's how contest programmers trust a greedy they can't quickly prove.

// Stress test: greedy vs brute force on random tiny inputs.
Random rng = new Random(42);
for (int t = 0; t < 100_000; t++) {
    int[][] input = randomSmallCase(rng);   // small n so brute force is feasible
    int g = greedy(input);
    int b = bruteForce(input);              // honest exponential ground truth
    if (g != b) {
        System.out.println("COUNTEREXAMPLE:");
        System.out.println(Arrays.deepToString(input));
        System.out.println("greedy=" + g + " brute=" + b);
        break;   // now you have a minimal case to study — the tell for WHY greedy is wrong
    }
}
Technique 1
Shrink the counterexample

Once the stress test prints a failing case, minimize it by hand (drop elements while it still fails). A 3-element counterexample usually reveals the exact wrong assumption in your choice.

Technique 2
Check the tie-breaks

Half of "almost-correct" greedies die on ties: equal end times, equal ratios, touching intervals. Make every comparator total and every >= vs > deliberate.

Technique 3
Verify the sort key against the proof

If you can't state a one-sentence exchange argument for your key, that's a red flag before you even run code. "I sort by end because finishing early leaves the most room" should roll off the tongue.

Technique 4
Watch overflow & comparator subtraction

a - b in a comparator overflows for large ints and corrupts the order. Use Integer.compare. Sums of many values may need long.

Debugging checklist (top to bottom)

① Can you state the exchange/staying-ahead proof in one sentence? ② Does the greedy match brute force on 100k random small cases? ③ Are ties (equal keys, touching intervals) handled deliberately? ④ Is the comparator total & overflow-safe (Integer.compare)? ⑤ For "min removals"-type answers, is it n − kept not kept? Nine times in ten it's ①, ②, or ③.

13 · Interview thinking — the monologue#

Here's the internal narration of someone solving a greedy problem cold. Notice they never "recall the trick" — they run the engine out loud, and they earn trust before coding. Steal this script.

"Restate and find the objective."#

"So I'm maximizing a count / minimizing a cost over a set of choices. That's optimization — greedy or DP. Let me test which."

"Is there a dominant first move?"#

"What's the obviously-best thing to commit to? Earliest finish? Best ratio? Let me name two or three candidates rather than marry the first."

"Let me try to break each."#

"Can I build a tiny case where 'earliest start' loses? …Yes, a long early interval. Kill it. Can I break 'earliest finish'? …I can't — and I can argue why: it leaves the most room." (Interviewers love watching you kill your own ideas.)

"State the proof sketch."#

"Staying-ahead: greedy's k-th finish is never later than anyone's k-th, so it never has fewer options. That's why it's safe." — even a one-line proof sketch massively raises interviewer confidence.

"Now the mechanics."#

"Sort by end — O(n log n). One pass, keep if start ≥ lastEnd. If the best move changed each step I'd use a heap; here a sort suffices."

"Sanity-check on the example, mention stress-testing."#

"Let me trace the given example… 4, matches. If I were unsure, I'd stress-test against brute force."

Interview meta-moves

• Never commit to the first sort key — voice 2–3 candidates and eliminate. • Always try to break your own greedy before coding; it signals rigor and often saves you. • Offer a one-sentence proof (exchange or staying-ahead) — it's the difference between "I think this works" and "this works, here's why." • If you can't prove it, say so and propose a stress test or fall back to DP. Honesty about greedy's fragility reads as senior.

14 · The problem roadmap#

Order matters. Each tier adds one idea on top of the last. Don't skip — the "why next" is the point. Finish a tier before the next.

Tier 0 · Feel the safe move easy#

  1. Assign Cookies Sort both sides, match greedily — the gentlest "sort then two-pointer" safe move.
  2. Maximum Units on a Truck Sort by units desc, take greedily. First "which key?" decision.
  3. Lemonade Change Give the largest bills first; a tiny exchange argument you can feel.

Tier 1 · Intervals — the flagship family medium#

  1. Non-overlapping Intervals Activity selection; derive "sort by end" via the widget's counterexamples.
  2. Minimum Arrows to Burst Balloons The same sweep; practice the inclusive/strict endpoint decision.
  3. Merge Intervals → Meeting Rooms II Contrast: sort by start; then the heap/event sweep for concurrency.

Tier 2 · One-pass reach & reset medium#

  1. Jump Game → Jump Game II Farthest-reach frontier; then the level-boundary min-jumps greedy.
  2. Gas Station Running-tank reset; prove why skipping the bad prefix is safe.
  3. Maximum Subarray Kadane as the cleanest reset-greedy; drop a negative prefix.

Tier 3 · Structures & harder proofs hard#

  1. Connect Sticks (Huffman) First heap-driven greedy; the two-smallest exchange argument.
  2. Remove K Digits Monotonic-stack greedy; building an extremal subsequence.
  3. Candy Two-pass greedy with both-sides constraints.
  4. Course Schedule III Sort by deadline + max-heap "undo the longest" — the peak heap greedy.

Tier 4 · Graph greedies & constructions hard#

  1. Min Cost to Connect All Points MST / cut property; Kruskal with DSU or Prim with a heap.
  2. Network Delay Time Dijkstra as greedy shortest path; contrast with MST.
  3. Largest Number Pairwise custom comparator + proving it's a valid order.
How to practice, not just solve

For each problem: (1) run the engine from scratch — name 2+ candidate choices and try to break them, don't peek; (2) write the one-sentence proof for your survivor before coding; (3) if you can't prove it, stress-test against brute force. If you solved it only by recognizing the pattern, redo it and force the proof. Understanding greedy = being able to re-derive the safe move and justify it cold, a week later.


15 · Cheat sheets#

Everything compressed. Screenshot these; they're the residue of the whole page.

The Discovery Engine, on one line each#

1 · objective 2 · brute-force oracle 3 · list candidate choices 4 · break the losers 5 · take survivor 6 · prove or stress-test 7 · sort/heap + one pass

Recognition checklist — "should I reach for greedy?"#

  • Objective is optimize / count / feasibility over a set of choices.
  • There's a dominant first move you can argue no optimum is hurt by taking.
  • After committing, the past collapses to a tiny summary (lastEnd, running total, a heap).
  • You cannot build a case where a locally-worse move wins globally (if you can → DP).
  • "Sort then sweep" or "repeatedly take the extreme" smells natural.

Sort-key heuristics — what dominance implies#

If the safe move is…Sort / structure byFamily
free the resource earliestend timeactivity selection, min arrows
process the timeline in orderstart timemerge intervals, meeting rooms
most value per unit budgetvalue/weight ratiofractional knapsack
grab the biggest prize that fitsprofit desc + slots (DSU)job sequencing
do the most urgent firstdeadlinemin max-lateness (EDF)
combine the two smallestmin-heapHuffman / connect ropes
nearest available ≥/≤ xTreeMap/TreeSetbooking, course schedule III
cheapest edge, no cycleedge weight + DSUMST (Kruskal)
extremal subsequencemonotonic stackremove K digits

Proof templates — fill in the blanks#

Exchange
Swap it in

"Take any optimal OPT. At the first place it differs from greedy, replace OPT's choice with greedy's. This stays valid because ___, and no worse because ___. Repeating turns OPT into greedy, so greedy is optimal."

Staying-ahead
Never behind

"Measure progress by ___. After step k, greedy's measure is ≥ any solution's because ___ (induction). Since greedy never falls behind, it can't finish worse."

Greedy vs DP — the one-question decision#

Is there a single first move I can commit to blindly, provably without regret?   Yes → greedy (commit, recurse, prove by exchange/staying-ahead).   No → DP (a locally-worse move can win globally, so you must compare futures and cache). Say this out loud on every optimization problem and you'll route correctly.

The mistakes that cost the most#

Don'tMarry the first sort key. Ship on "it feels optimal." Apply ratio-greedy to 0/1 knapsack. Use greedy for arbitrary-coin change. Ignore ties & touching endpoints. Subtract ints in a comparator.
DoGenerate candidates, break the losers. Prove or stress-test before trusting. Read "fractional/divisible" as the greedy licence. Handle ties deliberately. Use Integer.compare. State a one-line exchange proof.
Greedy was never about clever tricks. It's a search that never backtracks, licensed by a proof that it never needs to. The code is five lines; the skill is the licence. You've now seen the engine — propose, attack, prove — a dozen times. The last step is yours: open the roadmap, and on every problem, generate candidates, try to break them, and prove the survivor before you write a line. That habit — not this page — is what makes you someone who solves unseen greedy.