Greedy algorithms
A greedy algorithm makes the choice that looks best right now and never changes it. That makes it the fastest approach in this course, and it is also the reason it can silently return a wrong answer. This chapter covers three things: when a greedy choice is safe, how to prove it with an exchange argument, and what to do when the proof fails.
Fast, but it needs a proof
Greedy is not a data structure. It is a way of deciding: look only at the current step, and commit.
Here is a greedy algorithm you already run every day: making change. To give back 68 with notes of 50, 10, 5, and 1, a cashier does not solve an equation. She takes a 50, then a 10, then a 5, then three 1s. Each step takes the largest note that still fits. That is greedy: break a large problem into a sequence of small decisions, take the best-looking option at each one, and never go back.
That example works. But it works because of these particular note values, not because greedy always works. Change the values to 1, 3, and 4 and ask for 6: greedy takes the 4 first and needs three coins, while 3 + 3 needs only two. Same method, same kind of problem, wrong answer. Section 07 takes that example apart.
Compare greedy with the two approaches you already know. Backtracking (chapter 05) tries every choice and undoes the ones that lead nowhere: always correct, but exponential. Dynamic programming (next chapter) also considers every choice, but stores the answer of each subproblem so it is computed once: always correct, and polynomial. Greedy goes further than both: it keeps exactly one choice per step and throws the rest away. That is why it usually runs in O(n) or O(n log n), and it is also why it needs a proof. Dropping the other choices is only safe if you can show none of them was needed.
Greedy assumes one thing: the best choice at each step belongs to some best overall answer. When that holds, the problem has the greedy-choice property. When it does not, you need DP.
A choice is never undone, unlike backtracking. Not going back is what makes greedy fast, and it is also what makes it fragile: one wrong step can never be repaired.
Many greedy solutions start by sorting (455, 435, 452), because the sort decides in which order the decisions are made. So the cost of a greedy solution is often dominated by the O(n log n) of the sort.
Passing a few test cases is not a proof
Greedy code is short, and short code that passes a handful of examples feels finished. It is not. A classic failure: with coins [1, 3, 4] and amount 6, greedy takes the largest coin first and gets 4 + 1 + 1 = 3 coins, while the best answer is 3 + 3 = 2 coins. Greedy never looks at a plan that starts with a 3, because 4 looked better at the time. Section 07 opens that case up, and the next chapter builds the DP solution for it. A proof is the only thing separating a greedy algorithm from a guess that happened to work.
Huffman coding: a greedy algorithm that started as homework
In 1951, David Huffman was a graduate student at MIT. His professor, Robert Fano, offered a term paper problem: find the optimal prefix code. Fano and Claude Shannon had only found a top-down method that is not always optimal. Huffman was close to giving up when he tried the opposite direction: build the tree bottom up, repeatedly merging the two least frequent nodes. That is a greedy rule, and it is provably optimal. Huffman coding is still used inside JPEG, MP3, and ZIP. Once a greedy rule is proven, it is both fast and dependable.
The exchange argument
EASYFeatured problem A · LC 455 Assign Cookies — learn the proof first, then the code
The problem: each child i has an appetite g[i], and each cookie j has a size s[j]. One cookie goes to at most one child, and the child is satisfied only if s[j] ≥ g[i]. How many children can be satisfied at most?
The greedy rule: sort both arrays, then give the child with the smallest appetite the smallest cookie that is still large enough. If the smallest remaining cookie is too small even for that child, it is too small for every child, so discard it. Watch it run once before reading the proof.
The code is a sort plus one pass with two pointers. Short code is not the same as correct code, so the important question is why this rule cannot lose.
Now the proof. The tool is the exchange argument, and it is the template behind almost every greedy correctness proof in this chapter. The idea in one sentence: take any optimal solution that disagrees with the greedy choice, and rewrite it so that it agrees, without making it worse. If such a rewrite always exists, then some optimal solution contains the greedy choice, so making that choice loses nothing. Three steps:
Name the greedy choice, and take any optimal solution
Let c₀ be the child with the smallest appetite and s* the smallest cookie with s* ≥ g[c₀]. Greedy pairs them. (Every cookie smaller than s* satisfies no child at all, because c₀ is the least demanding one.) Now take any optimal assignment OPT and compare.
Rewrite OPT so that it pairs c₀ with s*
If OPT satisfies c₀ with another cookie s′, then s′ ≥ g[c₀] and therefore s′ ≥ s*. Give s* to c₀ instead. If s* was used by another child, hand that child s′; since s′ ≥ s*, he is still satisfied. If OPT leaves c₀ unsatisfied, then s* cannot be idle in OPT, otherwise OPT could feed one more child and would not be optimal. So s* is used by some child c′; give s* to c₀ and drop c′.
The count never drops, so induct on the rest
In every case the number of satisfied children stays the same, and the rewritten solution now makes the greedy first choice. Remove c₀ and s*: what remains is the same problem with one child and one cookie fewer. Repeat the argument there. After all steps, greedy has matched an optimal solution. That completes the proof.
An exchange argument is induction wearing a costume
Strip away the story and you get induction. The hypothesis is "there exists an optimal solution that agrees with greedy on the first k steps". The inductive step is "one rewrite that does not make things worse extends the agreement to step k + 1". Sorting-based greedy proofs (455, 435, 452, 56), activity selection, and Huffman coding all share this skeleton. In an interview you do not need the full write-up, but you should be able to say "I take the first place where an optimal solution differs from mine and swap it, and I show the swap does not make the solution worse". That sentence is the difference between a proof and a hunch.
How to state the proof out loud
"I sort both arrays and give the child with the smallest appetite the smallest cookie that satisfies him. For correctness I use an exchange argument: if an optimal assignment does not make that pairing, I can rewrite it so that it does, and the number of satisfied children does not drop. Repeating this on the remaining children shows my answer is optimal." Two sentences, and the claim is now backed by a reason instead of a feeling.
Sequence greedy: one decision per element
376 count direction changes · 53 Kadane (review) · 122 stocks, greedy and DP side by side
The lightest kind of greedy: one linear scan, with a small decision at each element based on its relation to the previous one. Start with LC 376 Wiggle Subsequence. A wiggle sequence is one where consecutive differences are non-zero and alternate in sign: up, down, up, down. The task is to find the longest wiggle subsequence.
The greedy observation: treat a stretch of equal values as a single value, then cut the array into maximal runs that go only up or only down. Inside a run, the middle elements contribute nothing, because they do not change direction. Keeping only the last element of each run keeps the wiggle just as long, so if there are k runs, the answer is k + 1 (the last element of each run, plus the first element of the array). In [1, 4, 7, 2, 5] the runs are 1→7, 7→2, and 2→5, so k = 3 and the answer is 4. The 4 in the middle is one stop on the way up (grey); the endpoints and the turning points 7 and 2 are what count (highlighted):
LC 53 Maximum Subarray (review): the greedy reading of Kadane's algorithm is if the sum carried in from the left is negative, it can only make the next subarray smaller, so drop it. That is cur = max(nums[i], cur + nums[i]), while tracking the largest cur seen. The same line is also a one-dimensional DP where cur is the largest sum of a subarray ending at index i. Chapter 07 teaches it in full, with a cell-by-cell animation. Here it is only a reminder that one problem can have two justifications.
LC 122 Best Time to Buy and Sell Stock II: greedy and DP agree
The problem: one price per day, unlimited transactions, at most one share held at a time. Maximise the profit. The greedy view: collect every rise, profit += max(0, p[i] − p[i−1]). Why that is optimal, in two parts. First, a trade that buys on day i and sells on day j earns p[j] − p[i], which equals the sum of all day-to-day differences in that range, so it is at most the sum of the positive ones. Trades never overlap, so each difference is counted at most once, making Σ max(0, Δ) an upper bound for every possible strategy. Second, buying and selling on each rising day reaches that bound exactly, so the bound is achieved and the greedy answer is optimal. The DP view: two states per day, holding a share or holding cash, with transitions for buy, sell, and do nothing (chapter 10). Two routes, same number:
One line, O(n) time and O(1) space. It works whenever you can show a multi-day gain equals the sum of its day-to-day rises, which is the sentence that carries the whole argument.
hold = max(hold, cash − p); cash = max(cash, hold + p). With a transaction fee (714) or a transaction limit (123), the greedy rule stops being optimal and only the state machine still works. Heavier to write, but general.
Jump game: track the reachable range, not the jumps
MEDIUMFeatured problem B · LC 55 can you arrive → LC 45 in how few jumps
LC 55: each number says how many steps forward you may jump from that cell. Can you get from index 0 to the last index? Brute force tries every jump length from every cell, which is exponential. The greedy observation: you never need to know which jumps to make. It is enough to track the farthest index reachable so far. The invariant is: when the scan arrives at index i and i ≤ reach, every index from 0 to i is reachable from the start, and reach is the farthest index reachable using cells 0 to i as launch points. So if i > reach, index i is unreachable, and because movement is forward only, nothing beyond it is reachable either. Here is the case where the range falls one cell short:
LC 55 needs three lines: reach = max(reach, i + nums[i]), return false if i > reach, and return true at the end. Turning "can you arrive" into "in how few jumps" gives LC 45 Jump Game II.
Brute force or DP: let dp[i] be the fewest jumps to reach i, and for each i look back at every cell that can jump to it. That is O(n²). Why O(n) is possible: group the indices into layers. Layer 1 is everything reachable in one jump, layer 2 everything reachable in two, and so on. The scan keeps two numbers: curEnd, the last index of the current layer, and farthest, the last index of the next layer. While walking inside a layer it only records how far the next layer could go; it counts a jump exactly when it reaches the end of the current layer. The invariant is that after k counted jumps, curEnd is exactly the farthest index reachable in k jumps, so the layer that first contains the last index gives the minimum. Step through it:
farthest == i while i is not the last index.LC 45 is breadth-first search with the queue removed
Treat each index as a node and "i can jump to j" as an edge. LC 45 then asks for the length of the shortest path, which breadth-first search answers by expanding one layer at a time. The greedy version stores each layer as two integers, curEnd for the end of the current layer and farthest for the end of the next one, so the queue disappears and both time and space drop to O(n) and O(1). Compressing a layered search into a single linear scan is what this family of greedy solutions is about. Hop-count estimation in network routing and minimum-move problems in games use the same shape.
Complexity and follow-up questions
LC 55 and LC 45 both run in O(n) time and O(1) space. Three questions come up often. (1) Why loop to n−2? You do not jump from the last cell, and looping to n−1 would count an extra jump when the last cell happens to be a layer boundary. (2) What does the DP version look like? dp[i] = min over all j that can reach i of dp[j] + 1, which is O(n²); it is worth showing as the starting point the greedy version improves. (3) What if the end is not guaranteed reachable? As soon as farthest == i and i is not the last index, nothing further is reachable, so return −1.
One local rule, one pass
860 change · 134 gas station · 135 candy · 406 queue reconstruction
These problems have no clever sorting trick. You work out one local rule and then simulate it in a single pass. All of the difficulty sits in one question: why does following that rule never cost you later?
LC 860 Lemonade Change (warm-up): customers pay with 5, 10, or 20, and you give change from the bills you have received. A 5 needs no change, a 10 needs one 5, and a 20 needs either 10 + 5 or 5 + 5 + 5. The rule: prefer 10 + 5. Here is the exchange argument in one step. Choosing 10 + 5 leaves you with two more 5s and one fewer 10 than the other option. Suppose a later customer forces you to use a 10 you no longer have. You can pay that 20 with 5 + 5 + 5 instead, which costs exactly those two extra 5s. So preferring 10 + 5 never turns a case you could have served into one you cannot.
LC 134 Gas Station: a circular route becomes one linear scan
The problem: n stations on a circular route. Station i holds gas[i] litres, and driving from i to i+1 costs cost[i] litres. Find a starting station from which you can complete the full circle. Two facts do all the work. (1) If Σgas < Σcost there is no answer, because the fuel over one full circle is not enough no matter where you start. (2) If you start at a and the tank first goes negative at station b, then no station between a and b works either. For any c in that range, the partial sum from a to c−1 was still non-negative, so the sum from c to b is at most the sum from a to b, which is negative. Starting at c therefore runs dry at station b at the latest. That lets you skip the whole block and continue from b+1, in a single O(n) pass.
total decides whether an answer exists, and tank decides where it starts. One pass covers both.LC 135 Candy (hard, but the pattern is clear): every child has a rating, a child with a higher rating than a neighbor must get more candy than that neighbor, and everyone gets at least one. Minimise the total. The difficulty is that each child is constrained from the left and from the right at the same time. The fix is one direction per pass: going left to right, satisfy "more than the left neighbor"; going right to left, satisfy "more than the right neighbor"; take the maximum so both hold. Why the result is minimal: each pass produces the smallest values that satisfy one side, so both are lower bounds for any valid answer, and so is their maximum. That maximum is itself valid, so it is the smallest valid assignment at every position, and therefore has the smallest total.
LC 406 Queue Reconstruction by Height: place the tall people first
The problem: each person is described by (h, k), meaning height h with exactly k people of height ≥ h in front. Rebuild the queue. The greedy rule: sort by height descending, then k ascending, and insert each person at index k of the result list. Why it is correct: everyone already placed is at least as tall as the person being inserted, so inserting a shorter person does not change how many taller-or-equal people stand in front of anyone already placed. And the new person lands with exactly k such people ahead, which is what (h, k) requires. Fixing the attribute with the largest effect first, then arranging the secondary one, is a pattern that repeats across sorting-plus-greedy problems.
Intervals: sort by start or by end?
MEDIUMFeatured problem C · LC 435 Non-overlapping Intervals, with 452 / 763 / 56 alongside
Almost every interval problem starts with a sort, and the choice of sort key decides whether it works. The main example is LC 435 Non-overlapping Intervals: given a set of intervals, remove as few as possible so that the rest do not overlap.
Brute force: try every subset to keep, which is 2ⁿ. The greedy observation: removing the fewest is the same as keeping the most non-overlapping intervals. To keep many, always keep the interval that ends earliest, because it leaves the largest amount of time for everything after it. So sort by right endpoint, scan left to right, keep an interval when it does not overlap the last kept one, and delete it otherwise. Step through the timeline:
key=lambda iv: iv[1] is the whole idea. Change it to iv[0] and the answer becomes wrong: one long interval that starts early can push out several short ones behind it.Why the end, and not the start? The exchange argument: let OPT be an optimal set of kept intervals, listed in order, and let X be its first interval. G, the interval that ends earliest of all, satisfies end(G) ≤ end(X). Replace X by G. The remaining intervals of OPT all start at or after end(X), so they start at or after end(G) as well, and the set is still non-overlapping and just as large. Repeat on the rest, and greedy keeps as many as any optimal solution.
The other two sort keys really do fail. Sorting by start time and keeping greedily: on [0,10], [1,2], [3,4], you keep [0,10] and end with 1 interval, while the best answer is 2. Sorting by shortest length: on [0,5], [4,6], [5,10], the shortest is [4,6], which overlaps both others, so you end with 1 interval instead of 2. Neither of those rules survives an exchange argument, and each has a three-interval counterexample.
Sort by end. Fire an arrow at the smallest end value; every balloon with start ≤ that value is burst. When one is out of range, fire a new arrow. The same problem in different clothes.
Record the last index of every letter first. While scanning, extend the right boundary of the current part to the farthest last index seen in it, and cut when the scan reaches that boundary. It merges the span of each letter.
The goal is to merge, not to select, so it sorts by start: extend the end with max when the next interval touches, and open a new one when it does not. Taught in chapter 01; here it is the contrast case.
| What you want | Sort key | Greedy step | Problems |
|---|---|---|---|
| Keep the most non-overlapping, delete the fewest, use the fewest arrows | end, ascending | keep the earliest ending one, leaving the most room | 435 · 452 · activity selection |
| Merge all overlapping intervals | start, ascending | extend the end if it touches, otherwise open a new one | 56 · 57 |
| Cut a sequence into parts by content | no interval sort; record each letter's last index | cut when the scan reaches the current right boundary | 763 |
Interval scheduling is everyday resource allocation
LC 435 is also known as activity selection. Booking a meeting room so that the most meetings fit, choosing tasks in a CPU scheduler, and allocating time slices for bandwidth or virtual machines are all the same problem. "Sort by finishing time and take the one that finishes first" has been a proven result in operations research for decades. When a calendar tool suggests a set of meetings that do not clash, it is usually running this.
Where greedy fails: if you cannot prove it, use DP
Counterexamples, and a rule for choosing between greedy and DP — the bridge to chapter 07
You now have a fast tool, and the warning from the start of the chapter still applies: greedy can fail. The clearest failure is the opening example of the next chapter, LC 322 Coin Change, with coins [1, 3, 4] and amount 6. Try it yourself and watch greedy miss the best answer:
Take the 4 (2 left), then 3 is too large, so 1 + 1. Every step was locally best, and the branch it entered cannot be undone. It never sees the plan that starts with a 3.
dp[6] tries all three possibilities for the last coin (1, 3, or 4) and takes the smallest result. It does not make a choice; it evaluates every choice. That is the safety net DP provides.
Why does greedy fail here? Because the coin set [1, 3, 4] does not have the greedy-choice property. Taking the 4 first is the locally best move, and it destroys the structure of the best answer, 3 + 3. There is no exchange argument to write, because the counterexample is right there. No proof, no greedy. The fallback is DP: enumerate which coin is used last, and store the answer of each subproblem. Chapter 07 does exactly that, with a cell-by-cell animation of dp[6]. Note also that the same greedy rule is optimal for other coin sets, such as 1, 5, 10, 25 — which is why the property has to be checked per coin set, not assumed.
The knapsack pair: same greedy rule, one version optimal, one not
This is the standard example of a greedy rule that is right in one setting and wrong in a very similar one. Both versions have items with a weight and a value, and a capacity limit. The greedy rule is the same: take items in decreasing order of value per unit of weight.
Fractional knapsack (you may take a fraction of an item): the rule is optimal, by an exchange argument. If a solution contains any weight of a lower-ratio item while a higher-ratio item is not fully taken, swap one unit of weight between them; the total value does not go down. Repeating this turns any optimal solution into the greedy one.
0/1 knapsack (each item is taken whole or not at all): the rule is not optimal. Capacity 10, and three items — A (weight 6, value 12), B (weight 5, value 9), C (weight 5, value 9). Greedy takes A first because its ratio is 2, then only 4 capacity is left and neither B nor C fits, giving 12. Taking B and C gives 18. The swap that works for fractions is impossible when items cannot be split, so the argument breaks and the answer breaks with it. The 0/1 version is solved with DP in the knapsack chapter.
Try greedy: can you state the exchange argument in one sentence?
If you can say in one sentence why the local choice cannot destroy a best answer (435: ending earlier leaves more room; 455: the smallest usable cookie wastes nothing), go ahead and be greedy. O(n log n) and done.
Cannot prove it? Look for a counterexample
Build small inputs designed to break the rule: coins [1, 3, 4] for 6, or sorting 435 by start time. One counterexample settles it. Do not hope that larger inputs will behave better.
Fall back to DP: enumerate every decision and store the results
List the possibilities for the last step, take the min or max, and store each subproblem answer so it is computed once. Slower than greedy, but polynomial, and always correct. That is the tool the next chapter hands you.
The order to think through an optimisation problem
When you meet an optimisation problem, walk through the three approaches in this order, which is also a good order to speak them out loud. Backtracking: enumerate every choice, always correct, exponential, useful only as a starting point. Greedy: one choice per step, fastest, but only valid with a proof, and already wrong on [1, 3, 4]. DP: enumerate every decision and store the results, always correct, polynomial. The short rule: prove the exchange argument and be greedy; fail to prove it and use DP. The next chapter follows that line from start to finish.
Problem set: 18 greedy problems
Core setGrouped as exchange argument, sequence, jump, simulation, intervals, from easier to harder. Think for 30 seconds before opening the hint.
Quiz
✎ QuizAnswer all 8 correctly to mark this chapter complete
A greedy algorithm returns an optimal answer only when the problem has which two properties?
What does an exchange argument actually prove?
LC 455 Assign Cookies, with appetite array g and cookie size array s. Which greedy rule is correct?
For which of these problems is sorting the intervals by right endpoint the correct choice? (Select all)
In LC 45 Jump Game II, when should the greedy scan increase the jump counter?
Intervals [[1,4], [2,3], [3,5], [6,8], [7,9]]. What is the smallest number of intervals you must remove so that the rest do not overlap? (Sort by right endpoint and work through it by hand.)
LC 122 adds up every rise between two consecutive days. Why is that optimal?
Coins [1, 3, 4], amount 6. Greedy (always take the largest coin that fits) gives 4 + 1 + 1 = 3 coins; the best answer is 3 + 3 = 2 coins. What is the right conclusion?
- Greedy means one locally best choice per step, never revisited. It is valid only when the problem has the greedy-choice property (a locally best choice belongs to some best overall answer) and optimal substructure. Fast, but it needs a reason.
- The exchange argument is the tool of this chapter: assume an optimal solution differs from greedy at the first step, rewrite that step into the greedy choice without making things worse, and induct. It is mathematical induction in applied form.
- The sort key decides interval problems: selecting, deleting the fewest, or covering with the fewest → sort by end (ending earlier leaves more room); merging → sort by start. Sorting by start time or by length breaks LC 435, and each has a three-interval counterexample.
- Jump problems track the reachable range, not the actual jumps. LC 55 keeps the farthest reachable index; LC 45 adds a layer boundary and counts one jump per layer, which is breadth-first search with the queue replaced by two integers.
- Simulation greedy is one local rule plus one pass, and each rule needs its own reason: 134 discards a failed prefix because every start inside it fails no later; 135 splits a two-sided constraint into two one-directional passes; 860 keeps the most flexible bill.
- Greedy and DP can both solve the same problem (122, 53). Greedy commits to one choice per step; DP keeps every choice and picks at the end. That is why greedy is faster and why it needs a proof.
- Where greedy fails is where DP starts: coins [1, 3, 4] for 6, and value-per-weight on the 0/1 knapsack. Both lose the exchange argument, so both fall back to enumerating every decision and storing the results. The rule in one line: prove it and be greedy, or use DP. See you in the next chapter.