DSA Notes · chapter 05 · Java Live Code

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

05

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 0 1 2 3 4 a[i] 2 3 1 1 4 reach 2 4 4 4 8 last index 4 is inside reach
Every index. Success: reach always stays ≥ i, and finally ≥ n−1.
ia[i]i > reach?reach beforereach after = max(reach, i+a[i])
02no (start)0max(0, 0+2) = 2
131 > 2? no2max(2, 1+3) = 4
212 > 4? no4max(4, 2+1) = 4
313 > 4? no4max(4, 3+1) = 4
444 > 4? no4max(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 0 1 2 3 4 a[i] 3 2 1 0 4 reach 3 3 3 3 ✕ i=4 is past reach 3
Every index until the fail. reach never moves past 3.
ia[i]i > reach?reach beforereach afternote
03no03can stand on 0,1,2,3
121 > 3? no3max(3, 1+2) = 3does not extend
212 > 3? no3max(3, 2+1) = 3does not extend
303 > 3? no3max(3, 3+0) = 3standing on 0 adds nothing
444 > 3? YES3stopnever 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.

Time O(n)Space O(1)
Why this

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.

Common mistakes
  • 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.
ProblemPatternLinks
Remove K digits (greedy + stack, not jump)drop peaks so the remaining digits are smallestJava in ch.05 · cheat card
“Minimum operations” — only after you name the stategreedy if no-regret; else BFS/DPclue

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.

day 0 1 2 3 4 5 price 7 1 5 3 6 4 minBuy 7 1 1 1 1 1 profit 0 0 4 4 5 5
Interview walk. Every day after day 0.
ipriceminBuy before sellprofit if sell todaybestminBuy after
077 (init)—07
1171−7 = −6 → ignore01
2515−1 = 441
3313−1 = 241
4616−1 = 551
5414−1 = 351

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.

Every day. Profit stays 0 until the spike.
ipriceminBuy beforep − minBuybestminBuy after
099—09
1797−9 = −207
2474−7 = −304
3343−4 = −103
4838−3 = 553

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.

Time O(n²) nested · O(n) running minSpace O(1)
Why this

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.

Common mistakes
  • 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).
ProblemPatternLinks
Buy/sell + trapping rain (same hour family in one IE)one-transaction min, not raincheat 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.

Greedy, largest first. Amount 12.
coin triedtimes takenremainder aftercoins used so far
10121
50 (5 > 2)21
1203

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.

greedy: 6 → take 4 → 2 → take 1 → 1 → take 1 → 0 3 coins optimal: 6 → 3 + 3 2 coins
Greedy walk (wrong answer 3).
leftlargest coin ≤ lefttakeleft aftercount
64421
21 (3 > 2)112
11103
DP fewest coins. INF = 7. Every amount 1..6, each coin. dp[a] = min over c ≤ a of dp[a−c]+1.
ac=1 → dp[a−1]+1c=3c=4dp[a]
1dp[0]+1 = 1skipskip1
2dp[1]+1 = 2skipskip2
3dp[2]+1 = 3dp[0]+1 = 1skip1
4dp[3]+1 = 2dp[1]+1 = 2dp[0]+1 = 11
5dp[4]+1 = 2dp[2]+1 = 3dp[1]+1 = 22
6dp[5]+1 = 3dp[3]+1 = 2dp[2]+1 = 32

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.”

Time Greedy O(k log k + k) after sort · DP O(k · amount)Space Greedy O(1) · DP O(amount)
Why this

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.

Common mistakes
  • 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.
ProblemPatternLinks
Remove K digitsmonotonic stack greedy — proof is “a left peak can go”Java in ch.05 · cheat card
Minimum operations (do not assume greedy)name the state firstclue

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.