// pattern debugger≡ menu

stack>advanced patterns / greedy

// Greedy

Take the locally best move and prove you never regret it. Recognizing when greedy works — and when it silently doesn't.

core idea

Greedy makes one irreversible, locally-best choice per step and never reconsiders it — no backtracking, no trying alternatives, no remembering the runner-up. That only pays off when you can prove the local choice never costs you the global optimum. Skip the proof and greedy still runs, still returns an answer, and is sometimes silently wrong — which is what makes it more dangerous to reach for on instinct than a pattern that simply fails loudly. The proof almost always takes one of three shapes:

proof style the argument example below
exchange argument swapping the greedy choice for any other choice can’t improve the answer sort-by-end interval scheduling (see intervals)
never-regret once made, no information that arrives later can make this choice worse than it already is Best Time to Buy and Sell Stock, Jump Game
restart-past-failure if the run dies at i, no start between the current candidate and i survives either — jump the candidate past all of them Gas Station

when to reach for it

  • A single forward pass with one running variable — a minimum, a frontier, a running total — is enough. You never need to remember every option seen, only the best one so far.
  • The question asks for a max/min produced by an irreversible sequence of choices: “maximum profit from one transaction”, “minimum jumps to the end”, “can you complete the circuit”.
  • You can state the exchange or never-regret argument in one sentence. If you can’t, you likely need dynamic programming instead — DP keeps every option alive until it’s provably dominated; greedy throws options away the moment it moves on.
  • Sorting first turns the problem into one clean pass — intervals is the deepest example of this move.

universal templates

Track & decide — the shape behind three of the four problems below:

public int TrackAndDecide(int[] nums)
{
    var best = InitialBest();          // the answer if we stopped right now
    var running = Seed(nums[0]);       // the one number greedy needs to remember

    for (int i = 1; i < nums.Length; i++)
    {
        best = Combine(best, running, nums[i]);   // score this step against what's tracked
        running = Update(running, nums[i]);       // fold today into the running invariant
    }
    return best;
}

running is a minimum (cheapest price seen), a maximum (furthest reachable index), or a running sum (fuel remaining) depending on the problem — the shape of the loop never changes, only what Update and Combine compute.

Extend a boundary until it closes — Partition Labels’ shape:

public List<int> ExtendUntilClose(string s, Dictionary<char, int> lastIndex)
{
    var result = new List<int>();
    int start = 0, end = 0;

    for (int i = 0; i < s.Length; i++)
    {
        end = Math.Max(end, lastIndex[s[i]]);   // widen to cover this item's whole span
        if (i == end)                            // nothing after i can pull the boundary further
        {
            result.Add(end - start + 1);
            start = i + 1;
        }
    }
    return result;
}

prove it before you code it

Before writing the loop, say the proof out loud: “if I’d made a different choice here, could it ever beat this one?” A clean no means greedy is safe. If the honest answer is “well, it depends what comes later” — it doesn’t work, and reaching for dynamic programming is the right move, not a fallback to feel bad about.

problems

Four problems, three proof shapes: buy-sell-stock and jump-game are both never-regret arguments with different running invariants, gas-station is restart-past-failure, and partition-labels is the boundary-close template wearing an interval-merge costume.

  1. Track the cheapest buy so far; every day asks "sell today?"

  2. 02Jump GamemediumLC #55

    Furthest-reachable frontier — one variable, one pass.

  3. 03Gas StationmediumLC #134

    If you run dry at i, no start before i works — restart at i+1.

  4. 04Partition LabelsmediumLC #763

    Greedy + hashmap: extend the partition to each char's last index.

cheat sheet — greedy

recognize it

  • a single forward pass with one running variable (a min, a frontier, a running total) is all the state you need — no need to remember every option seen, just the best one
  • the question asks for a max/min produced by an irreversible sequence of choices: maximum profit from one transaction, minimum jumps, can you complete the circuit
  • you can state a never-regret or exchange argument in one sentence (swapping this choice for any other can't help) — if you can't, it's probably dynamic-programming, not greedy
  • sorting first turns the problem into one clean left-to-right pass

key tricks

  • Math.Max/Math.Min fold replaces a whole DP table when dp[i] only ever depends on dp[i-1] — that's the buy-sell-stock/Kadane compression
  • restart-past-failure: if the run fails starting at i, every start < i also fails, so jump straight to start = i + 1 and reset — no need to re-check the discarded range
  • extend-the-boundary-until-it-closes: end = Math.Max(end, lastIndex[x]), cut the moment i == end
  • frontier tracking: farthest = Math.Max(farthest, i + nums[i]), bail the instant i > farthest
  • when the greedy proof doesn't hold, don't force it — fall back to the full DP table and compare costs (Jump Game shows both side by side)

common bugs

  • assuming greedy always works because the loop compiles and returns something plausible — it needs an actual never-regret/exchange proof, not vibes
  • forgetting the floor/failure case: unreachable frontier isn't false unless you check it, total < cost must map to -1, an all-decreasing price array must floor at 0
  • getting the order of Update vs Combine backwards on a min/max fold — e.g. updating minPrice before scoring today's profit can silently change what a step computes
  • off-by-one on when a greedy boundary is allowed to close: i == end vs i >= end, or nums.Length vs nums.Length - 1 as the goal index

// connections

  • Intervals — interval scheduling is greedy's home turf — sort, then never regret
  • Dynamic Programming Basics — when the greedy proof fails, DP is the fallback — Jump Game shows both
  • Binary Search — the CanDo(k) check inside binary-search-on-answer is usually greedy
  • Array & Matrix Techniques — Buy-Sell Stock and Kadane are the same scan with different bookkeeping