Skip to content
All reading material
Patterns·13 min read

Greedy

Take the locally optimal choice and never reconsider — when that yields the global optimum, the proof techniques that justify it, and the classic greedies.


The idea

A greedy algorithm builds the answer by always taking the best-looking choice right now, never backtracking. It’s simple and fast (often one pass after a sort), but only correct when local optimality implies global optimality. The hard part isn’t coding it — it’s knowing (and proving) that greedy is valid here rather than DP.

When greedy works

Two properties must hold:

  • Greedy-choice property — a globally optimal solution can be reached by making the locally optimal choice at each step.
  • Optimal substructure — an optimal solution contains optimal solutions to subproblems.

If a greedy choice can paint you into a corner (a locally great move that forecloses the true optimum), it’s a DP problem instead. When unsure, hunt for a counterexample — one breaks greedy; failing to find one (plus a proof) supports it.

Proving a greedy (the part interviewers probe)

  • Exchange argument — take any optimal solution and show you can swap its choices for the greedy ones, one at a time, without making it worse → greedy is at least as good.
  • “Greedy stays ahead” — show that after each step, the greedy partial solution is at least as good as any other on the relevant measure.
  • Matroid theory — the formal umbrella for why many greedies (spanning trees, scheduling) are optimal; name-drop only if relevant.

Worked examples

Jump game — track the furthest reachable index; if you fall behind it, you’re stuck:

def can_jump(nums):
    reach = 0
    for i, n in enumerate(nums):
        if i > reach: return False
        reach = max(reach, i + n)
    return True

Interval scheduling — sort by end time, take each interval starting after the last taken ends (earliest-finish is provably optimal — exchange argument). See the intervals primer.

Kadane (max subarray) — drop the running sum the moment it goes negative (a greedy reset).

The classic greedies (recognize them)

  • Activity / interval selection — earliest finish time.
  • Fractional knapsack — highest value/weight ratio first (the 0/1 knapsack is DP, not greedy — a classic trap).
  • Huffman coding — repeatedly merge the two least-frequent nodes (a heap-driven greedy).
  • Dijkstra / Prim — always expand the nearest/cheapest frontier node (greedy + heap; see advanced graphs).
  • Jump Game II — fewest jumps via a BFS-like furthest-reach greedy.
  • Gas Station — if total gas ≥ total cost, the start is just after the lowest running deficit.
  • Task Scheduler, Hand of Straights, Partition Labels, Candy, Assign Cookies, Merge/Remove for monotonic — sort or count, then sweep.

Greedy vs DP (the boundary)

GreedyDP
Choicecommit, never revisittry all, keep the best
SpeedO(n) / O(n log n)often O(n·states)
Valid whengreedy-choice property holdsoverlapping subproblems, choices interact
Examplefractional knapsack, intervals0/1 knapsack, edit distance

If choices interact (taking one changes the value of others in non-local ways), reach for DP.

Pitfalls

  • Assuming greedy without proof — many “obvious” greedies are wrong (0/1 knapsack, coin change with arbitrary denominations).
  • Wrong sort key — the whole correctness often hinges on sorting by the right field (end time, ratio, deadline).
  • Edge cases — empty input, ties, all-equal elements.

Choosing in an interview

State your greedy rule, then immediately justify it (“sort by end time; by an exchange argument, picking the earliest-finishing interval never hurts”) or find a counterexample and switch to DP. The justification — not the code — is what’s being tested.