Greedy
Make the best local choice at each step and prove it never hurts, usually after sorting.
- O(n log n)Sort, then one pass
- O(n)One pass with a running best, O(1) space
- coins 4, 3, 1Greedy fails at 6: three coins, not two
Show what to say
“When a problem asks for a maximum or minimum count and a simple rule like earliest end, cheapest so far or furthest reach looks right, I try greedy: usually a sort plus one pass. I only trust it with a proof, an exchange argument or greedy stays ahead; if a small counterexample breaks it, I switch to DP.”
Trade-offs
- Greedy vs DP on coin change: greedy is one quick pass but can be trapped; DP costs O(amount × coins) and cannot be.
- Earliest end vs earliest start or shortest: only earliest end stays ahead; the other two fail on small examples.
- Exchange argument vs stays ahead: exchange swaps the greedy choice into any optimal solution; stays ahead shows greedy is never behind on some measure.
Bring it up before they ask
Attack your own rule on two to four items before coding, and watch ties: start > lastEnd rejects intervals that merely touch, where >= accepts them.
Greedy takes whatever looks best right now and never takes it back: usually a sort plus one pass. It is correct only when you can prove the choice is safe. A small counterexample means DP.
When to reach for it
Tap the signals you can see in the problem.
Rememberrule of thumbsorting
- Running best
The greedy rulekeep the best so far
buy and sell stock - Sort, then match
The greedy rulesmallest item, smallest slot it fits
assign cookies - Earliest end first
The greedy rulesort by end, keep what fits
non-overlapping intervals
The core idea: a choice you never regret
Picture handing out cookies: each child wants a cookie of at least some size.
Read more · 87 words
Give the smallest cookie that works to the least greedy child. A bigger one can't do better here and may be needed later.
Greedy code is easy; knowing the choice can't hurt is hard. Name the proof:
- Exchange argument: take any best answer. If it skips your choice, swap yours in. It is still valid and no worse.
- Stays ahead: show greedy is never behind, e.g. its k-th interval ends no later than any other schedule's.
No proof? Hunt for a counterexample, then use dynamic programming.
Sort, then match: the smallest cookie that satisfies the least greedy child
Each child has a greed factor (the smallest cookie that makes them happy), and each cookie can go to one child. First, sort both lists.
function findContentChildren(greed, cookies) {
greed.sort((a, b) => a - b);
cookies.sort((a, b) => a - b);
let child = 0;
for (let c = 0; c < cookies.length && child < greed.length; c++) {
if (cookies[c] >= greed[child]) child++;
}
return child;
}All steps as text
- Each child has a greed factor (the smallest cookie that makes them happy), and each cookie can go to one child. First, sort both lists.
- Now walk the cookies from smallest to largest, always trying the least greedy child who is still waiting.
- Cookie 1 >= greed 1: give it to this child. Smallest cookie left, least greedy child left: any best assignment can be rearranged to pair them like this without losing a happy child (the exchange argument).
- Cookie 1 < greed 2. This child is the least greedy one still waiting, so nobody left can use this cookie: throw it away.
- Cookie 2 >= greed 2: this child is content.
- Cookie 3 >= greed 3: this child is content.
- Cookie 5 >= greed 4: this child is content.
- Stop: no cookies are left. 4 of 5 children are content, and no other assignment does better.
Play with it
Now a choice you do regret: change-making.
Read more · 100 words
Race greedy against DP on coins 4, 3, 1, or type your own coins and press Find a trap.
Step by step
Write the brute force in one sentence
"Try every subset of intervals" names the choices and the goal.
Propose a rule, usually after sorting
Earliest end, smallest that fits, cheapest so far, furthest reach.
Attack your own rule
Try to break it on 2–4 items. Interviewers expect this.
Prove it, or drop it
Make the exchange or stays-ahead argument. If a counterexample survives, use DP.
Implement the one pass
A sort plus a loop. Watch ties (
>=versus>).
Templates
Template 1: sort, then match
Pairing items with slots (cookies, boats, workers)? Sort both and walk them together. A cookie too small for the least greedy child is useless, so throw it away.
// Template 1: sort, then match greedily. Each child needs a cookie of at least greed[i]; each cookie feeds one child.
// Walk the cookies smallest first and give each one to the least greedy child still waiting, if it is big enough.
export function findContentChildren(greed, cookies) {
const g = [...greed].sort((a, b) => a - b);
const c = [...cookies].sort((a, b) => a - b);
let child = 0;
for (let i = 0; i < c.length && child < g.length; i++) {
if (c[i] >= g[child]) child++; // feed this child; a too-small cookie is useless to everyone left
}
return child;
}Template 2: earliest end first
To keep the most activities that don't overlap, keep the one that ends first among those that still fit. Earliest start and shortest length both fail.
This solves Non-overlapping IntervalsMedium (removals = total − kept). More in Intervals.
Earliest end first: the most activities that don't overlap
We want as many activities as possible with no two overlapping (one may start exactly when another ends). Sort them by end time.
function maxNonOverlapping(intervals) {
intervals.sort((a, b) => a[1] - b[1]);
let kept = 0, lastEnd = -Infinity;
for (const [start, end] of intervals) {
if (start >= lastEnd) {
kept++;
lastEnd = end;
}
}
return kept;
}All steps as text
- We want as many activities as possible with no two overlapping (one may start exactly when another ends). Sort them by end time.
- Nothing kept yet. The rule: take the activity that ends first among those that still fit.
- [1,3) starts at 1, not before the last kept end (none yet), so it fits.
- Keep it. Of everything that fits, it ends earliest, so our schedule of 1 activity finishes at 3: no schedule of 1 activity can finish sooner.
- [2,5) starts at 2, before the last kept activity ends at 3: it overlaps, skip it.
- [4,6) starts at 4, not before the last kept end (3), so it fits.
- Keep it. 2 activities, finishing at 6: still as early as any schedule of 2 activities can finish.
- [6,8) starts at 6, not before the last kept end (6), so it fits.
- Keep it. 3 activities, finishing at 8: still as early as any schedule of 3 activities can finish.
- [5,9) starts at 5, before the last kept activity ends at 8: it overlaps, skip it.
- [8,10) starts at 8, not before the last kept end (8), so it fits.
- Keep it. 4 activities, finishing at 10: still as early as any schedule of 4 activities can finish.
- 4 activities kept. Because the greedy schedule always finished earliest, it always had at least as much room left as any other: it "stays ahead", so 4 is the maximum.
// Template 2: interval scheduling. Keep the most non-overlapping intervals [start, end) by always taking
// the one that ends first among those that still fit. After k picks, no other choice of k intervals ends sooner.
export function maxNonOverlapping(intervals) {
const sorted = [...intervals].sort((a, b) => a[1] - b[1]);
let kept = 0;
let lastEnd = -Infinity;
for (const [start, end] of sorted) {
if (start >= lastEnd) { // fits after the last kept interval (touching is fine)
kept++;
lastEnd = end;
}
}
return kept;
}Template 3: one pass with a running best
Many greedy solutions never sort: one scan, one number that sums up the past.
Read more · 46 words
- Best Time to Buy and Sell StockEasy: the cheapest price so far.
- Jump GameMedium: the furthest reachable index.
- Jump Game IIMedium: count frontier extensions.
- Gas StationMedium: running dry at station
irules out every start before it. - Partition LabelsMedium: cut when the scan reaches the furthest last occurrence.
Need both directions? Make two passes, as in CandyHard.
let reach = 0; // Jump Game: furthest index reachable so far
for (let i = 0; i < nums.length; i++) {
if (i > reach) return false; // a gap nothing can cross
reach = Math.max(reach, i + nums[i]);
}
return true;When greedy fails
"Make change with the fewest coins: take the largest coin that fits."
Read more · 59 words
Coins 4, 3, 1 and amount 6: greedy pays 4 + 1 + 1. But 3 + 3 needs only two. The 4 felt like progress, then trapped you.
The fix: stop committing. Dynamic programming finds the best answer for every smaller amount, trying each coin as the last one.
The same split: fractional knapsack is greedy, 0/1 knapsack needs DP.
When greedy fails: making change with the largest coin first
- amount
- 68
Make 68 with as few coins as possible. Greedy rule: always take the largest coin that still fits. Sort the coins largest first.
function greedyChange(coins, amount) {
coins.sort((a, b) => b - a);
const used = [];
for (const coin of coins) {
while (amount >= coin) {
amount -= coin;
used.push(coin);
}
}
return amount === 0 ? used : null;
}All steps as text
- Make 68 with as few coins as possible. Greedy rule: always take the largest coin that still fits. Sort the coins largest first.
- No coins used yet.
- 25 fits: take it. 43 left to make.
- 25 fits: take it. 18 left to make.
- 25 is bigger than the 18 still needed, so move on to the next smaller coin.
- 10 fits: take it. 8 left to make.
- 10 is bigger than the 8 still needed, so move on to the next smaller coin.
- 5 fits: take it. 3 left to make.
- 5 is bigger than the 3 still needed, so move on to the next smaller coin.
- 1 fits: take it. 2 left to make.
- 1 fits: take it. 1 left to make.
- 1 fits: take it. 0 left to make.
- Greedy used 7 coins, and that is the true minimum (a DP over every amount agrees). For this coin system, taking the largest coin is always safe.
// Greedy change-making: take the largest coin that fits, again and again. Returns the coins used, or null.
// Optimal for "canonical" systems like [25, 10, 5, 1], but not for arbitrary coins.
export function greedyChange(coins, amount) {
const desc = [...coins].sort((a, b) => b - a);
const used = [];
for (const coin of desc) {
while (amount >= coin) {
amount -= coin;
used.push(coin);
}
}
return amount === 0 ? used : null;
}
// The safe answer: dp[x] = fewest coins that make x, trying every coin as the last one. O(amount × coins).
export function minCoins(coins, amount) {
const dp = new Array(amount + 1).fill(Infinity);
dp[0] = 0;
for (let x = 1; x <= amount; x++) {
for (const coin of coins) if (coin <= x) dp[x] = Math.min(dp[x], dp[x - coin] + 1);
}
return dp[amount] === Infinity ? -1 : dp[amount];
}Try it
// Edit the inputs and press Run. The functions are the templates above.
console.log(findContentChildren([3, 1, 6, 2, 4], [2, 1, 5, 3, 1])); // 4
console.log(maxNonOverlapping([[5, 9], [1, 3], [6, 8], [2, 5], [8, 10], [4, 6]])); // 4
console.log(greedyChange([25, 10, 5, 1], 68), minCoins([25, 10, 5, 1], 68)); // [25, 25, 10, 5, 1, 1, 1] 7
console.log(greedyChange([4, 3, 1], 6), minCoins([4, 3, 1], 6)); // [4, 1, 1] 2 (greedy is wrong here)Complexity
Read more · 33 words
| Variant | Time | Space |
|---|---|---|
| Sort, then one pass | O(n log n) | O(1) to O(n) |
| One pass, running best | O(n) | O(1) |
| Coin change by DP | O(amount × coins) | O(amount) |
JavaScript you'll use
JavaScript refresher: Sorting and comparators Numbers, Math and integer tricksRead more · 33 words
sort works in place: copy with [...arr] if the order matters later.
const sorted = [...items].sort((a, b) => a - b); // copy first if the caller's order matters
intervals.sort((a, b) => a[1] - b[1]); // by end time
let best = -Infinity, cheapest = Infinity; // sentinels: any real value beats them
best = Math.max(best, price - cheapest); // Infinity arithmetic is safe: 5 - Infinity is -Infinity
const total = nums.reduce((sum, x) => sum + x, 0); // feasibility totals (gas station)Interview questions
How do you convince an interviewer your greedy is correct?
Name the argument (exchange, or stays ahead) and walk their example through it. Offer the DP if you can't argue it.
For which coin systems does greedy change-making work?
"Canonical" ones like 1, 5, 10, 25. Coins 1, 3, 4 fail at 6. With arbitrary coins, use DP.
Greedy or dynamic programming: how do you decide quickly?
Ask whether an early choice can be regretted. Test a few tiny inputs against a brute force.
Check yourself
Question 1 of 3Score 2 out of 3 or better to mark this page complete.
Practice problems
Solve these in the browser editor. Each runs against hidden tests; the step-by-step solution unlocks after a wrong submission.
- Best Time to Buy and Sell StockPick one day to buy and a later day to sell so that the profit is as large as possible.Easy
- Jump GameEach value says how far you may jump from that index. Decide whether you can reach the last index.Medium
- Jump Game IIEach value says how far you may jump from that index. Find the fewest jumps that reach the last index.Medium
- Gas StationFind the station from which a car with an empty tank can drive once around a circular route without running dry.Medium
- Partition LabelsCut a string into as many pieces as possible so that every letter appears in only one piece, and return the piece sizes.Medium
- CandyHand out the fewest candies so every child gets one and anyone rated higher than a neighbour gets more than that neighbour.Hard
- Container With Most WaterPick two vertical lines that, with the x-axis, hold the most water.Medium
- Non-overlapping IntervalsRemove as few intervals as possible so that none of the remaining ones overlap.Medium
- Task SchedulerFind the least time to finish all tasks when equal tasks must be separated by a cooldown.Medium
In real systems
This pattern is not only for coding rounds. Here is where it does the work in a design interview.