awesome-everything RU
↑ Back to the climb

Algorithms from zero

Greedy: code reading

Crux Read real greedy snippets: an interval scheduler with the wrong sort key, a greedy that loses to DP, and a Gas Station scan. Predict the behaviour and pick the highest-leverage fix.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min

Greedy bugs are quiet: the code runs, returns something plausible, and is wrong only on the inputs you did not test. Read each snippet the way you would in review — find the choice that is not provably safe, then pick the fix a senior engineer makes first.

Goal

Practise the loop you run on every greedy: read the local choice, decide whether it is provably optimal, and reach for the right sort key, the right invariant, or DP — before trusting the output.

Snippet 1 — the interval scheduler

// Goal: pick the MAXIMUM number of non-overlapping intervals.
function maxNonOverlapping(intervals) {
  const sorted = [...intervals].sort((a, b) => a[0] - b[0]); // sort by START
  let count = 0, lastEnd = -Infinity;
  for (const [start, end] of sorted) {
    if (start >= lastEnd) { count++; lastEnd = end; }
  }
  return count;
}
// maxNonOverlapping([[1, 10], [2, 3], [4, 5]])  ->  ?
Quiz

What does this return on [[1,10], [2,3], [4,5]], and what is the one-line fix?

Snippet 2 — a greedy that loses to DP

// "Fewest coins for `amount`" by always taking the largest coin that fits.
function greedyCoins(coins, amount) {
  const sorted = [...coins].sort((a, b) => b - a);
  let left = amount, count = 0;
  for (const c of sorted) {
    while (left >= c) { left -= c; count++; }
  }
  return left === 0 ? count : Infinity;
}
// greedyCoins([1, 5, 6, 9], 11)  ->  ?   (DP optimum is 2: 5 + 6)
Quiz

greedyCoins([1,5,6,9], 11) returns 3 (9+1+1) but the DP optimum is 2 (5+6). Which statement correctly diagnoses this?

Snippet 3 — the Gas Station scan

function canCompleteCircuit(gas, cost) {
  let total = 0, tank = 0, start = 0;
  for (let i = 0; i < gas.length; i++) {
    const diff = gas[i] - cost[i];
    total += diff;
    tank += diff;
    if (tank < 0) { start = i + 1; tank = 0; }
  }
  return total >= 0 ? start : -1;
}
Quiz

A reviewer wants to delete the `total` variable and return `start` directly, arguing the reset already finds the answer. Why must `total` stay?

Snippet 4 — Jump Game II loop bound

function jump(nums) {
  let jumps = 0, curEnd = 0, farthest = 0;
  for (let i = 0; i < nums.length; i++) {   // loops to n-1
    farthest = Math.max(farthest, i + nums[i]);
    if (i === curEnd) { jumps++; curEnd = farthest; }
  }
  return jumps;
}
// jump([2, 3, 1, 1, 4])  ->  ?   (true minimum is 2)
Quiz

This returns 3 on [2,3,1,1,4] but the minimum is 2. What is the off-by-one bug, and the fix?

Recap

Every greedy is read the same way: name the local choice, then ask whether it is provably safe. Snippet 1 used the wrong sort key (start instead of finish) for maximization; snippet 2 used greedy on a denomination set without the greedy-choice property, where only DP is correct; snippet 3 showed the global feasibility invariant (total >= 0) is load-bearing, separate from the local reset; snippet 4 was the classic Jump Game II off-by-one (loop to n-2, never jump from the goal). Diagnose the unsafe choice, then fix the key, the invariant, or fall back to DP.

Continue the climb ↑Greedy: a scheduler you can prove correct
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.