Local best, with a counterexample in your pocket
A greedy choice is safe only when you can say why no later option beats it. Jump’s running reach and one-transaction stock’s running min have that sentence. Coin systems do not, unless you prove them. If you cannot prove it, write DP or produce [1,3,4] amount 6.
Prev: 04 Dynamic Programming · Next: 06 Binary Search
Greedy
Three cards: jump reachability, one buy/sell, and the coin-change counterexample. Each has simple Java, interview Java, a tiny example, and a second example that walks every iteration.
Jump Game — reachability, not min jumps
Recognize it from
“From index i you may jump up to a[i] steps forward. Can you reach the last index?” Sometimes they then ask the fewest jumps (Jump Game II). First question is boolean: does a zero trap you.
What it actually does
The greedy fact: you never need to choose a particular jump length. Track reach = the farthest index that any legal standing position can land on. Scan left to right. If the scan index walks past reach, that cell is dead — nothing behind you could jump here, so the rest of the array is unreachable.
This is not a heap of remaining fuel and not DP of min jumps unless they asked for the count. Min jumps (Jump Game II) still uses a greedy window: when i hits the end of the current jump, bump the jump count and open a new window to the farthest seen.
Simple — read this first
public boolean canJump(int[] a) {
int reach = 0;
for (int i = 0; i < a.length; i++) {
if (i > reach) return false; // this index was never in range of an earlier jump
reach = Math.max(reach, i + a[i]); // farthest landing from anywhere we could stand
}
return true;
}
Interview — type this
public boolean canJump(int[] a) {
int n = a.length;
if (n <= 1) return true;
int reach = 0;
for (int i = 0; i < n && i <= reach; i++) {
reach = Math.max(reach, i + a[i]);
if (reach >= n - 1) return true; // early exit: last index is already in range
}
return reach >= n - 1;
}
Example 1 — simple
[2, 3, 1, 1, 4] — you can reach the end (for example 0 → 1 → 4).
| i | a[i] | i > reach? | reach before | reach after = max(reach, i+a[i]) |
|---|---|---|---|---|
| 0 | 2 | no (start) | 0 | max(0, 0+2) = 2 |
| 1 | 3 | 1 > 2? no | 2 | max(2, 1+3) = 4 |
| 2 | 1 | 2 > 4? no | 4 | max(4, 2+1) = 4 |
| 3 | 1 | 3 > 4? no | 4 | max(4, 3+1) = 4 |
| 4 | 4 | 4 > 4? no | 4 | max(4, 4+4) = 8 |
Answer: true. At i = 1, reach already covers the last index.
Example 2 — every step
[3, 2, 1, 0, 4] — the 0 at index 3 is a wall. maxReach dies there.
| i | a[i] | i > reach? | reach before | reach after | note |
|---|---|---|---|---|---|
| 0 | 3 | no | 0 | 3 | can stand on 0,1,2,3 |
| 1 | 2 | 1 > 3? no | 3 | max(3, 1+2) = 3 | does not extend |
| 2 | 1 | 2 > 3? no | 3 | max(3, 2+1) = 3 | does not extend |
| 3 | 0 | 3 > 3? no | 3 | max(3, 3+0) = 3 | standing on 0 adds nothing |
| 4 | 4 | 4 > 3? YES | 3 | stop | never reached; a[4] is irrelevant |
Answer: false. The fuel from 3, 2, 1 all expire at index 3. The 4 on the far side is a tease.
Farthest-reach is a running max. You do not need to try every jump length from i: any index ≤ reach is a legal stand, so extending from the best stand so far dominates. DP boolean reachable[i] is correct and slower to type.
- Returning true because the last cell is a large number. You still have to get there.
- Stopping the loop at n−2 and forgetting to check whether reach covers n−1.
- Writing min-jumps windows when they only asked can-you-reach.
| Problem | Pattern | Links |
|---|---|---|
| Remove K digits (greedy + stack, not jump) | drop peaks so the remaining digits are smallest | Java in ch.05 · cheat card |
| “Minimum operations” — only after you name the state | greedy if no-regret; else BFS/DP | clue |
Best Time to Buy and Sell — one transaction, running min
Recognize it from
“One buy and one sell later. Max profit, or 0 if you should not trade.” Not unlimited transactions (sum every uphill). Not cooldown / at most k deals — those are a small state machine, still DP.
What it actually does
Profit of a pair (buy day i, sell day j > i) is prices[j] − prices[i]. For a fixed sell day, the best buy is the minimum price strictly before that day. So walk left to right: keep the running minimum, try selling today, then maybe update the minimum.
You never need the whole history, only that one integer. Nested loops make the pair visible; they are O(n²) and not what you type in Live Code.
Simple — read this first
public int maxProfit(int[] prices) {
int best = 0;
for (int i = 0; i < prices.length; i++) {
for (int j = i + 1; j < prices.length; j++) {
best = Math.max(best, prices[j] - prices[i]); // buy i, sell j
}
}
return best;
}
Interview — type this
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) return 0;
int minBuy = prices[0]; // cheapest price seen so far is the only buy you need
int best = 0;
for (int i = 1; i < prices.length; i++) {
int p = prices[i];
best = Math.max(best, p - minBuy); // sell today against that running min
minBuy = Math.min(minBuy, p); // a new low is a better future buy
}
return best;
}
Example 1 — simple
[7, 1, 5, 3, 6, 4]. Buy at 1, sell at 6, profit 5.
| i | price | minBuy before sell | profit if sell today | best | minBuy after |
|---|---|---|---|---|---|
| 0 | 7 | 7 (init) | — | 0 | 7 |
| 1 | 1 | 7 | 1−7 = −6 → ignore | 0 | 1 |
| 2 | 5 | 1 | 5−1 = 4 | 4 | 1 |
| 3 | 3 | 1 | 3−1 = 2 | 4 | 1 |
| 4 | 6 | 1 | 6−1 = 5 | 5 | 1 |
| 5 | 4 | 1 | 4−1 = 3 | 5 | 1 |
Answer: 5.
Example 2 — every step
Long decrease, then a spike: [9, 7, 4, 3, 8]. You must not sell during the slide. The only positive trade is buy 3, sell 8.
| i | price | minBuy before | p − minBuy | best | minBuy after |
|---|---|---|---|---|---|
| 0 | 9 | 9 | — | 0 | 9 |
| 1 | 7 | 9 | 7−9 = −2 | 0 | 7 |
| 2 | 4 | 7 | 4−7 = −3 | 0 | 4 |
| 3 | 3 | 4 | 3−4 = −1 | 0 | 3 |
| 4 | 8 | 3 | 8−3 = 5 | 5 | 3 |
Answer: 5. A decreasing prefix never produces a sell. Updating minBuy before computing today’s profit would let you “buy and sell today” for 0 — harmless here, but it hides the “sell against a previous min” rule; compute profit first, then update the min.
One transaction means one valley and one later peak. The valley that matters for a peak is the lowest price to its left. Unlimited trades would add every positive adjacent diff — a different problem. Rain-water is two-pointer / stack, not this state.
- Updating minBuy before subtracting, then wondering why same-day trades appear in a dry run (profit 0, so the answer can still be right).
- Summing all uphills when they said one transaction.
- Returning a negative. Empty or falling array is 0 (do not trade).
| Problem | Pattern | Links |
|---|---|---|
| Buy/sell + trapping rain (same hour family in one IE) | one-transaction min, not rain | cheat card · clue |
When greedy is false — coin change counterexample
Recognize it from
“Fewest coins to make amount.” Greedy (always take the largest coin that fits) is correct for canonical systems like US coins. It is not a theorem for arbitrary coin sets. If you cannot prove no-regret, write DP (unbounded knapsack) or say the counterexample.
What it actually does
Greedy subtracts the largest coin, repeats. Locally that shrinks the remainder fast. Globally it can paint you into leftover 1s that a pair of medium coins would have covered.
The DP is unbounded: dp[a] = fewest coins to make a. For each coin c you may reuse it because dp[a−c] is allowed to already contain c. That is the upward capacity loop — the opposite of 0/1 partition in the DP chapter.
Simple — read this first
public int coinChangeGreedy(int[] coins, int amount) {
Arrays.sort(coins); // largest first
int n = 0, left = amount;
for (int i = coins.length - 1; i >= 0; i--) {
int c = coins[i];
while (left >= c) {
left -= c;
n++;
}
}
return left == 0 ? n : -1; // looks right on US coins; not always optimal
}
Interview — type this
public int coinChange(int[] coins, int amount) {
int INF = amount + 1;
int[] dp = new int[amount + 1];
Arrays.fill(dp, INF);
dp[0] = 0; // zero coins make amount 0
for (int a = 1; a <= amount; a++) {
for (int c : coins) {
if (c <= a) dp[a] = Math.min(dp[a], dp[a - c] + 1); // unbounded: c may already sit inside dp[a-c]
}
}
return dp[amount] >= INF ? -1 : dp[amount];
}
Example 1 — simple
Canonical coins [1, 5, 10], amount 12. Greedy and optimal agree.
| coin tried | times taken | remainder after | coins used so far |
|---|---|---|---|
| 10 | 1 | 2 | 1 |
| 5 | 0 (5 > 2) | 2 | 1 |
| 1 | 2 | 0 | 3 |
Answer: 3 coins (10+1+1). DP also returns 3 (10+1+1, or 5+5+1+1, etc.). Greedy happened to be optimal — that does not prove the next coin set.
Example 2 — every step
Coins [1, 3, 4], amount 6. Greedy: 4+1+1 = 3 coins. Optimal: 3+3 = 2 coins.
| left | largest coin ≤ left | take | left after | count |
|---|---|---|---|---|
| 6 | 4 | 4 | 2 | 1 |
| 2 | 1 (3 > 2) | 1 | 1 | 2 |
| 1 | 1 | 1 | 0 | 3 |
| a | c=1 → dp[a−1]+1 | c=3 | c=4 | dp[a] |
|---|---|---|---|---|
| 1 | dp[0]+1 = 1 | skip | skip | 1 |
| 2 | dp[1]+1 = 2 | skip | skip | 2 |
| 3 | dp[2]+1 = 3 | dp[0]+1 = 1 | skip | 1 |
| 4 | dp[3]+1 = 2 | dp[1]+1 = 2 | dp[0]+1 = 1 | 1 |
| 5 | dp[4]+1 = 2 | dp[2]+1 = 3 | dp[1]+1 = 2 | 2 |
| 6 | dp[5]+1 = 3 | dp[3]+1 = 2 | dp[2]+1 = 3 | 2 |
Answer: DP 2 (the c=3 transition at a=6). Greedy’s 3 is a lie for this set. Keep this pair in your mouth: “greedy 4+1+1, optimal 3+3.”
Greedy needs a matroid / canonical-coin proof. You rarely have one on a random integer list. Unbounded DP is the honest fallback. Remove-k-digits is a different greedy that does have a proof: a left peak is never needed in a smallest number — monotonic stack, not coin change.
- Shipping greedy coin change because it worked on [1,5,10]. Always test [1,3,4] amount 6.
- Using the 0/1 reverse loop here. Coins may be reused; that is the upward / “for each amount, for each coin” nest.
- Returning greedy count when leftover ≠ 0 without checking that 1 exists.
| Problem | Pattern | Links |
|---|---|---|
| Remove K digits | monotonic stack greedy — proof is “a left peak can go” | Java in ch.05 · cheat card |
| Minimum operations (do not assume greedy) | name the state first | clue |
See also
Jump reachability and one-transaction stock are the two greeds you can prove in a sentence (running max reach; running min buy). Coin change is the card you use when someone says “greedy is always OK.” Remove-k-digits is stack + greedy — type it from chapter 05, not as a third full dry-run here.