latency.lab
Algorithms · Topic 14

Greedy

Level: foundationTool: exchange arguments and single-pass stateTime: ~75 min

By the end you can

A greedy algorithm makes the locally best choice at every step and never revisits it — no backtracking, no keeping a table of alternatives the way DP does. That makes greedy solutions cheap when they work (usually a single O(n) or O(n log n) pass), but it also means greedy is the one technique in this course where "it feels right" is not evidence. A greedy strategy is either provably correct or it's just a guess that happens to pass a few examples — the gap between those two is an exchange argument: showing that any solution which deviates from the greedy choice can be transformed into one that follows it, without getting worse.

Why greedy needs proof, not intuition

Best Time to Buy and Sell Stock II is a clean example. The greedy rule — capture every positive day-over-day gain — sounds almost too simple. The exchange argument: any longer upward run's total gain equals the exact sum of its individual daily gains, so splitting a run into single-day buy/sell pairs never loses anything, and skipping a positive day-over-day gain can only lose profit. Assign Cookies has a similar argument: handing a big cookie to a child who doesn't need it can never help, because it either satisfies that child (which a smaller cookie could have done just as well) or fails to help while removing a cookie that a greedier child needed. In both cases, the greedy rule wins because deviating from it is provably never better — not because it "seems reasonable."

The habit to build

Before trusting a greedy idea, ask: if some other, non-greedy choice were made at this step instead, can I show the result is never strictly better? If you can't articulate that argument, you don't yet have a greedy solution — you have an untested hypothesis. Several problems in this topic (Gas Station, Candy) are exactly the ones where the "obvious" first-instinct rule turns out to be wrong.

3
-5
4
-1
2
▲new start
Gas Station, net gain (gas − cost) per station: [3, −5, 4, −1, 2]. Starting from station 0, the tank is fine after station 0 (+3) but goes negative at station 1 (3 − 5 = −2). BOTH station 0 and station 1 (faded) are disqualified at once: starting from station 1 alone would only forfeit the +3 already banked, so it can never do better — the new candidate jumps straight past both, to station 2.

Pattern: one running value across a single pass

The cheapest greedy shape tracks exactly one piece of state and updates it once per element. Best Time to Buy and Sell Stock tracks the lowest price seen so far and checks the profit if selling today. Jump Game tracks the furthest index reachable so far. Gas Station tracks a running tank total and resets the candidate starting station the moment that tank goes negative — the exchange argument there is subtle: if the tank goes negative arriving at station i having started from some earlier station, every station between that start and i is disqualified too, since starting later only forfeits some of the positive contributions already banked. That's what makes a single pass enough instead of retrying every station independently.

Pattern: sort first, then pair from both ends

Some greedy problems only become tractable after sorting exposes the right pairing. Assign Cookies walks the least-greedy child against the smallest cookie in sorted order. Boats to Save People sorts by weight and always tries pairing the heaviest remaining person with the lightest remaining person — the heaviest person must ride in some boat regardless of who joins them, so pairing them with whoever is most likely to fit is never worse than any other pairing choice.

Pattern: track a feasible range instead of one exact value

Valid Parenthesis String looks like it needs backtracking — a * could be (, ), or nothing, and trying all three per wildcard branches exponentially. The greedy fix: track the range [lo, hi] of possible unmatched-open-paren counts, where lo assumes every * so far was a close and hi assumes every one was an open. One pass, one range, no branching — the range collapses the exponential number of individual interpretations into two numbers that summarize all of them at once.

Pattern: a monotonic stack for "smallest valid ordering"

Remove Duplicate Letters needs the lexicographically smallest ordering that still keeps every letter's relative order intact. The greedy rule: build a stack, and before pushing a new letter, pop the stack's top letter if it's larger than the new one and it reoccurs later in the string. That last condition is what makes popping safe — without checking that the popped letter still has a future occurrence, popping it could permanently lose it from the result.

When one direction isn't enough: Candy

Candy is the topic's clearest counter-example to "greedy means one simple pass." Every child needs more candy than a lower-rated neighbor on either side, and a single left-to-right (or right-to-left) pass can only ever see one of those two directions. The fix is two passes: enforce the left-neighbor constraint scanning forward, then enforce the right-neighbor constraint scanning backward, taking the max of what each pass computed rather than letting the second pass overwrite the first. Neither pass alone knows about the other's requirement, so combining them with max is what makes both constraints hold simultaneously.

Common mistakes

  • Trusting a greedy idea without an exchange argument — a rule that fits a couple of examples isn't yet a proof; Gas Station and Candy are exactly the two problems in this topic where the "obvious" first guess is wrong.
  • Spending a flexible resource before a rigid one — Lemonade Change's $10 bill is useless as change except when paired with exactly one $5, while a $5 can complete change for either denomination; spending the $10 first (when available) preserves more future options.
  • Enforcing only one direction of a two-directional constraint — Candy's rating comparisons look both left and right; a single pass structurally cannot see both at once, no matter how it's written.
  • Re-deriving reachability from scratch at each index — Jump Game and Jump Game II only need the single best (furthest) reach discovered so far; tracking anything more (like which specific earlier index produced it) is unnecessary bookkeeping.

Takeaways

  • Greedy correctness comes from an exchange argument (deviating from the rule is provably never better), not from a rule merely sounding reasonable.
  • The cheapest greedy shape tracks one running value across a single pass — a minimum, a furthest reach, a tank total.
  • Sorting first is often what turns an intractable pairing problem into a simple two-pointer greedy.
  • A feasible range (like Valid Parenthesis String's [lo, hi]) can replace an exponential number of individually-tracked branches.
  • Some constraints are inherently two-directional and genuinely need two passes, combined with max — not a cleverer single pass.

Try it: one running value, one pass

Best Time to Buy and Sell Stock — the simplest greedy in this topic. Track a single running minimum and check the profit at every step.

Live

Loading starter code…

Try it: the restart-on-negative exchange argument

Gas Station — the trickiest correctness argument in this topic. Work out for yourself why disqualifying an entire range of starting stations at once is safe before you look at the hint.

Live

Loading starter code…

Try it: when one pass genuinely isn't enough

Candy — the clearest counter-example to "greedy always means one simple pass." Notice exactly where a single direction of scanning loses information the other direction needs.

Live

Loading starter code…

Keep going

That's 3 of the 12 problems in this topic. Gas Station and Candy are worth sitting with the longest — they're the two places in this topic where the first-instinct rule is wrong, and working out why is the whole point of practicing exchange arguments. See the full Greedy set →

Checkpoint · Greedy

5 questions · pass at 70%

Q01
Best Time to Buy and Sell Stock II's greedy rule (capture every positive day-over-day gain) sounds almost too simple. What's the actual argument for why it's correct, not just a rule that happens to work on examples?
WhyThe exchange argument decomposes total profit over any path into the sum of daily gains along it. Since a run's total gain equals the sum of its parts, there's no cost to capturing every positive part individually, and skipping one strictly loses that gain -- that's what makes the rule provably optimal, not just plausible.
Q02
In Lemonade Change, when a customer pays with a $20 bill and both a $10 and enough $5s are available, why does the greedy strategy prefer giving one $10 + one $5 over three separate $5s?
WhyA $10 bill can only ever be handed back as change to a $20 customer. A $5 bill is far more valuable to keep on hand because it's needed by both $10 and $20 customers. Spending the narrowly-useful bill first (when possible) is the exchange argument: it never costs anything and can only help preserve flexibility for later customers.
Q03
In Gas Station, when the running tank goes negative arriving at station i (having started the attempt from some earlier station), why is it safe to restart the candidate starting station at i+1, rather than separately re-testing every station between the old start and i?
WhyStarting later in the disqualified range means giving up some of the positive tank contributions that were already counted -- it can never produce a better (less negative) tank at station i than starting earlier in that same range did. So the entire range up through i is safely eliminated at once, which is exactly what makes a single O(n) pass sufficient instead of an O(n^2) retry-every-station approach.
Q04
Valid Parenthesis String tracks a range [lo, hi] of possible unmatched-open-paren counts instead of trying all three interpretations of every '*' individually. What does that range actually represent, and why does it avoid exponential blowup?
WhyEach '*' independently could be '(', ')', or empty -- naively trying every combination is 3^n. Tracking [lo, hi] collapses that: lo is the most pessimistic reading (every '*' used as ')') and hi is the most optimistic (every '*' used as '('). Any interpretation in between is implicitly covered by the range, so one O(n) pass replaces an exponential search.
Q05
Candy needs two passes (left-to-right, then right-to-left, combined with max) rather than one. Why can't a single pass in either direction correctly enforce the full constraint?
WhyThe constraint is inherently bidirectional: a child needs more candy than EITHER a lower-rated left neighbor OR a lower-rated right neighbor. A forward pass can enforce the left-neighbor half of that constraint as it goes, but has no way to know about a right neighbor that hasn't been visited yet (and vice versa for a backward pass). Combining both passes with max is what makes both halves of the constraint hold at once.

Finished Greedy?

Pass the quiz to complete it automatically.