A greedy algorithm commits to the locally best choice and never undoes it. It’s correct when some optimal solution starts with the greedy choice and the rest is the same problem again. Unproven greedy is the most common source of WA, so the proof technique matters as much as the rule.

Finding the rule

  1. Brute force small inputs and look at optimal solutions. What’s chosen first? In what order?
  2. Try sort keys: value, deadline, end time, ratio , difference , sum , or a comparator derived from two elements (below).
  3. Stress test the candidate rule against a brute force before coding the real thing.

Adjacent exchange: finding the sort key

For “order items to minimise a cost”, look at two adjacent items. Put before iff , and rearrange it into .

Smith’s rule (minimise on one machine): first costs , first costs . So goes first iff .

More keys derived the same way:

ObjectiveSort by
shortest processing time first
(lateness)earliest deadline
ascending (compare as )
boxes/people stacking with strength and weight , minimise max load
concatenate numbers into the largest number as strings
tasks with requirement to start and cost : complete as many as possible descending
dragons/monsters where defeating needs strength and gives first by ascending; then by descending
two-machine flow shopJohnson’s rule (Scheduling)

Transitivity

The pairwise rule must be a strict weak ordering. Ratios, differences and sums are fine. For an unusual rule, test transitivity on random triples, or prove it by writing the comparison as . When ties matter (equal keys with different effects), add a second key and re-prove.

Exchange for selections

Take an optimal solution and the greedy solution . At the first difference, swap in greedy’s choice; show feasibility is kept and the objective doesn’t get worse. By induction, some optimal solution equals .

A second proof style, potential / bound, needs no exchange: find an a priori lower bound on the objective (an invariant, a counting argument, a matroid rank), then show greedy’s output meets it exactly. This is often shorter when the bound is easy to state (e.g. the number of components merged, a sum over a fixed set of constraints) but the exchange step would be fiddly.

Greedy stays ahead is the same idea measured stepwise: after steps, greedy’s progress is at least as good as any solution’s after steps. Interval scheduling by earliest finish: the -th greedy interval ends no later than the -th of any feasible schedule.

Regret greedy (priority queue with undo)

Take tentatively. When the constraint breaks, throw away the worst item taken so far. The proof is an exchange: the discarded item is never better than what replaced it.

// unit jobs with deadlines d and profits p: maximise total profit
sort(all(jobs));                                   // by deadline
priority_queue<ll, vector<ll>, greater<>> taken;   // min-heap of profits
for (auto [d, p] : jobs) {
    taken.push(p);
    if ((int)taken.size() > d) taken.pop();        // too many jobs before deadline d
}

The same shape covers:

  • Moore-Hodgson (fewest late jobs): drop the longest job taken so far when the deadline is violated.
  • Minimum refuelling stops: when out of fuel, refuel at the best station passed.
  • Buy low, sell high with unlimited transactions and one share per day (CF 865D): push each price twice. When the current price beats the cheapest in the heap, take the profit and push the current price so the trade can later be “extended”.
  • Choosing non-adjacent elements with maximum sum: take the maximum , replace it and its neighbours by (a regret node), repeat times with a heap and a linked list. This is a simple instance of the general “simulate min-cost flow augmentations with heaps” technique.

Greedy as simulated flow

Many greedy-with-heap solutions are min-cost flow where every augmenting path is short (one or two edges) and can be found with heaps. If a problem is naturally a flow (matching people to items on a line, buy and sell, assigning tasks with capacities) and is -, look for the greedy that simulates the augmentations, with “regret” entries representing reverse edges.

Huffman coding and optimal merging

Repeatedly merge the two smallest weights. The cost (sum of weights times depth, or the total merge cost of sorted runs) is minimised.

  • after sorting: two queues, one of sorted leaves and one of merged nodes (merged sums come out in non-decreasing order).
  • -ary Huffman: pad with zero-weight leaves until , then merge the smallest each time.
  • If the order of leaves must be kept (alphabetic codes, optimal BST-like merging of adjacent piles), greedy fails: use Hu-Tucker or Garsia-Wachs, or the interval DP with Knuth’s optimisation (Knuth-Yao).
  • Adaptive Huffman: update the tree as symbols stream in with no upfront frequency table (FGK/Vitter algorithms).
  • Canonical Huffman: reassign codes by (length, symbol order) only, so the code table transmits as just a list of lengths.
  • Arithmetic / range coding: a different entropy coder that beats Huffman on skewed distributions by not rounding each code to a whole number of bits; used alongside Huffman-style merging in JPEG, DEFLATE and MP3.
  • The same two-smallest-first merge minimises total cost for merging sorted runs in external sorting (optimal merge patterns) — it’s the same proof, with “merge cost” standing in for “weighted depth”.

Classic greedy table

ProblemRule
max non-overlapping intervalsearliest end
min points stabbing all intervalssort by end, place a point at each uncovered end
min roomsmax overlap depth; assign with a min-heap of end times
fractional knapsackvalue / weight descending
assign tasks to workers, maximise matches with sort both, two pointers
minimise sort both, pair in order
maximise sort both the same way (rearrangement inequality)
jump game, min jumpsBFS-like ranges: furthest reach per layer
canonical coin systemslargest coin first (check canonicity by brute force up to the sum of the two largest coins)
MSTKruskal / Prim (cut property)
matroid maximum weight basissorted greedy (Matroids)

Recognising a matroid is itself a correctness proof — no exchange argument needed, since greedy-by-weight is always optimal on a matroid’s independent sets (Matroids). Common matroids worth spotting on sight: spanning forests (graphic matroid, Kruskal), linearly independent vectors over a field or ( linear/XOR basis), partition matroids (at most items from group ), uniform matroids (any items), and scheduling unit jobs against deadlines (a transversal matroid — this is exactly why the regret-greedy construction above works).

When greedy fails

ProblemWhy
0/1 knapsackindivisible items; ratio order isn’t optimal
coin change with , target 6largest-first gives 3 coins, optimum is 2
weighted interval schedulingone long valuable interval vs several short ones; DP
set cover, vertex cover, TSPNP-hard; greedy gives only approximations (, 2, )
graph coloringdepends on vertex order (optimal on interval/chordal graphs with the right order)

An exchange argument that doesn’t close is evidence the greedy is wrong. Look for the counterexample with 3-4 elements.

Practice

CSES: Movie Festival, Tasks and Deadlines, Stick Lengths, Room Allocation, Factory Machines (binary search + greedy). Codeforces: the “greedy” tag, rating 1200–2000, is the densest source of exchange-argument practice.