DAY 27 / 30 · WEEK 4

Greedy Algorithms

Goal of the day

Concept

A greedy algorithm makes the choice that looks best right now, commits to it, and never looks back. No backtracking, no reconsidering, no comparing against alternatives you didn't take. Contrast that with dynamic programming (Days 25–26): DP considers — or caches the outcome of — every relevant subproblem, then combines those cached answers into a provably-correct result. Greedy skips all of that bookkeeping and just takes the best-looking option at each step. That makes greedy dramatically cheaper when it's valid... and silently wrong when it isn't.

"When it's valid" is the whole game. A greedy strategy is only safe to use when the problem has the greedy-choice property: the locally optimal choice at each step is provably always part of some globally optimal solution. If you can't prove that — or worse, if you can find even one input where the locally-best choice leads somewhere worse — greedy is not safe, and you need DP's exhaustive subproblem search instead.

Today has two worked examples that sit on opposite sides of that line:

Diagram: interval scheduling — greedy sorted by end time

Sorted by END time, then walked left to right (greedy keep/discard) 012 345 [1,2] KEEP → lastEnd=2 [2,3] KEEP → lastEnd=3 [1,3] REMOVE (1 < lastEnd 3) [3,4] KEEP → lastEnd=4 kept — start ≥ lastEnd, no overlap removed — start < lastEnd (overlaps last kept) Only [1,3] gets removed — 1 total. Ending each kept interval as EARLY as possible (sorting by end time) is what leaves the most room for the rest.

Diagram: when greedy fails — coin change with [1, 3, 4], amount 6

Coin-change counter-example — coins [1, 3, 4], target amount 6 GREEDY (largest coin ≤ remaining, each step) Pick 4 → remaining 2 Pick 1 → remaining 1 Pick 1 → remaining 0 Total: 3 coins (4 + 1 + 1) ✗ not optimal — 1 coin too many OPTIMAL (true minimum-coin solution) Pick 3 → remaining 3 Pick 3 → remaining 0 — done in 2 picks, not 3 Total: 2 coins (3 + 3) ✓ optimal — fewest coins possible Greedy commits to the coin 4 immediately and can never revisit that choice — yesterday's Day 26 DP checks every option exhaustively and correctly finds 3 + 3.

Interactive visualization: the interval greedy walk

Watch Non-overlapping Intervals run step by step: sort by end time, then walk left to right, keeping anything that doesn't overlap the last kept interval.

currently checking kept (non-overlapping) removed (overlaps last kept)

The dashed blue line marks lastEnd — the boundary the next interval's start must clear to be kept.

Press Play or Step to begin.

The code

// Non-overlapping Intervals (LC 435) — greedy, sorted by END time.
// Returns the minimum number of intervals to remove so the rest don't overlap.
function eraseOverlapIntervals(intervals) {
  if (intervals.length === 0) return 0;

  // The single most important line: sort by END time, not start time.
  // Whichever interval finishes earliest leaves the most room for what follows —
  // that's the greedy-choice property holding for THIS ordering specifically.
  intervals.sort((a, b) => a[1] - b[1]);

  let removals = 0;
  let lastEnd = intervals[0][1];        // end time of the most recently KEPT interval

  for (let i = 1; i < intervals.length; i++) {
    const [start, end] = intervals[i];
    if (start < lastEnd) {
      removals++;                       // overlaps the last kept interval — discard it
    } else {
      lastEnd = end;                    // no overlap — this becomes the new "last kept"
    }
  }
  return removals;
}

// The classic counter-example: greedy does NOT work for arbitrary coin change.
// Coins [1, 3, 4], amount 6 — "always take the largest coin you can afford."
function greedyCoinChange(coins, amount) {
  const sorted = coins.slice().sort((a, b) => b - a); // largest first
  let remaining = amount;
  const picks = [];
  for (const coin of sorted) {
    while (remaining >= coin) {
      picks.push(coin);
      remaining -= coin;
    }
  }
  return picks; // for [1,3,4], amount 6 -> [4, 1, 1] — 3 coins, NOT optimal (3+3 = 2 coins)
}

Line by line:

Dry run: eraseOverlapIntervals([[1,2],[2,3],[3,4],[1,3]])

Interval (sorted by end)lastEnd beforestart < lastEnd?Actionremovals after
[1,2] (first)keep (nothing to compare against yet) — lastEnd = 20
[2,3]22 < 2? Nokeep — lastEnd = 30
[1,3]31 < 3? Yesremove (overlaps [2,3])1
[3,4]33 < 3? Nokeep — lastEnd = 41

Input [[1,2],[2,3],[3,4],[1,3]] sorts by end time to [1,2],[2,3],[1,3],[3,4]. Only [1,3] overlaps a kept interval — final answer: 1 removal.

Complexity

ApproachTimeSpaceWhy
eraseOverlapIntervals (full)O(n log n)O(log n) – O(n)The sort dominates the runtime — the greedy scan after it is a single O(n) pass. Space depends on the sort implementation: some comparison sorts use O(log n) recursion stack, others (like some engines' array sort) can use up to O(n) auxiliary space.
The greedy walk alone (already sorted)O(n)O(1)One pass, tracking only lastEnd and a counter — no extra data structures.

Interview corner

How to talk through it out loud: state the greedy choice explicitly before writing any code — "I'll sort by end time and keep whatever doesn't overlap the last kept interval" — then say why it's safe: "the earliest-finishing interval always leaves the most room, so this choice can't be wrong." If you're not sure a greedy choice is safe, say so out loud and propose testing it against a small adversarial example before committing — that's exactly the instinct interviewers are listening for, and it's the difference between "I got lucky" and "I can prove this is correct."

Practice problems

1. Best Time to Buy and Sell Stock II (Easy — LeetCode: Best Time to Buy and Sell Stock II)

Given daily stock prices, return the maximum profit you can make with unlimited transactions (buy then sell, any number of times, but you must sell before buying again). This is a different LeetCode problem from the single-transaction "Best Time to Buy and Sell Stock" from earlier in the course — this one allows as many trades as you want.

2. Non-overlapping Intervals (Medium — LeetCode: Non-overlapping Intervals)

Given a list of intervals, return the minimum number of intervals to remove so the rest don't overlap. Today's main worked example — the code above is the real solution.

3. Jump Game (Medium — LeetCode: Jump Game)

Given an array where nums[i] is the maximum jump length from index i, starting at index 0, return whether you can reach the last index.

Quiz

1. What does the "greedy-choice property" mean?

2. In interval scheduling, why sort by END time instead of START time?

3. What does the coin-change counter-example (coins [1,3,4], amount 6) prove?

4. How do you recognize, in an interview, that a problem is NOT greedy-safe?

5. What's the time complexity of the Non-overlapping Intervals greedy solution?