Greedy Algorithms
Commit to the choice that looks best at each step and never revisit it. Fast and simple, but only correct when you can prove that choice is safe.
How It Works
A greedy algorithm builds its answer one step at a time. At each step it commits to whatever looks best right now and never goes back. When that's correct, it's usually the simplest solution there is. Most greedy solutions take one of two shapes: sort the input so the best candidate comes first and take what fits, or make one pass while tracking a single running value, such as the farthest index you can reach.
Jump Game, Step by Step
Step 1 of 13
nums
- Reachable so far
- Index being visited
- Visited
- Out of reach
Can you get from index 0 to the last index of [2, 3, 1, 1, 4]? Start with farthest = 0: so far you can only stand where you start.
All 13 steps as text
- Can you get from index
0to the last index of[2, 3, 1, 1, 4]? Start withfarthest = 0: so far you can only stand where you start. - Index
0is within reach (0 <= 0). A jump from it lands anywhere up to0 + 2 = 2, past0, sofarthest = 2. - Index
1is within reach (1 <= 2). A jump from it lands anywhere up to1 + 3 = 4, past2, sofarthest = 4. That covers the last index. - Index
2is within reach (2 <= 4), but2 + 1 = 3doesn't beatfarthest = 4, so it stays. - Index
3is within reach (3 <= 4), but3 + 1 = 4doesn't beatfarthest = 4, so it stays. - Index
4is within reach (4 <= 4). A jump from it lands anywhere up to4 + 4 = 8, past4, sofarthest = 8. - The loop visited every index without passing
farthest, so the last index is reachable.canJumpreturnstrue. - Now try
[3, 2, 1, 0, 4]. Start over withfarthest = 0. - Index
0is within reach (0 <= 0). A jump from it lands anywhere up to0 + 3 = 3, past0, sofarthest = 3. - Index
1is within reach (1 <= 3), but1 + 2 = 3doesn't beatfarthest = 3, so it stays. - Index
2is within reach (2 <= 3), but2 + 1 = 3doesn't beatfarthest = 3, so it stays. - Index
3is within reach (3 <= 3), butnums[3] = 0, so you can't jump on from it.fartheststays3. - Index
4is pastfarthest = 3. No index before it can jump that far, socanJumpreturnsfalse.
Greedy is safe when the problem has the greedy-choice property: some optimal answer starts with the greedy choice, and what's left afterwards is a smaller copy of the same problem. You show it with an exchange argument. Take any optimal answer that disagrees with greedy's first choice, swap greedy's choice in, and check that the result is still valid and no worse. If that swap always works, greedy never loses.
For example, to fit the most non-overlapping meetings into one room, keep taking the meeting that ends first among those that don't clash with the ones you've already taken. Whatever meeting an optimal schedule starts with, you can swap in the earliest-ending one instead. It ends no later, so it can't run into any meeting the schedule had after it. The Intervals topic uses the same rule.
Greedy fails when the best move now leaves you worse off later. Coin change is the classic trap. With coins [1, 3, 4] and amount 6, taking the largest coin that fits gives 4 + 1 + 1, three coins, while 3 + 3 needs only two. With US coins (1, 5, 10 and 25 cents) the same rule happens to work, which is exactly why it's tempting.
coins = [1, 3, 4], amount = 6 greedy 4 + 1 + 1 3 coins optimal 3 + 3 2 coins
When a counterexample like that exists, you have to compare the choices instead of trusting one, which is what Dynamic Programming: 1D does for coin change. Before you trust a greedy rule, spend a minute hunting for a small input that breaks it. If you can't find one, try to write the exchange argument.
When to Reach for It
- The question asks for a minimum or maximum, like the fewest jumps or the most meetings, and one move looks clearly best at each step.
- Sorting by a single key, such as end time, deadline or size, makes every decision easy.
- The input is large (100,000 items or more), so an O(n²) dynamic program won't fit, but a single pass would.
- You only need one running value, like the farthest point reached or a running balance, rather than every possible state.
Complexity
| Operation | Time | Space |
|---|---|---|
| One pass with a running value (Jump Game, Gas Station) | O(n) | O(1) |
| Sort, then one pass (most non-overlapping meetings) | O(n log n) | O(1) to O(n), depending on the sort |
| Repeatedly take the best item from a heap | O(n log n) | O(n) |
The Template
Jump Game
Given nums, where nums[i] is the longest jump you can make from index i, can you get from index 0 to the last index? You don't need to decide which jump to take. Track farthest, the farthest index any jump so far can reach. Every index up to farthest is reachable too, because a jump from i can land anywhere from i + 1 to i + nums[i], not just at the end.
So walk left to right and extend farthest with i + nums[i]. If you ever stand on an index past farthest, nothing before it can reach it, and the answer is false. If the loop finishes, every index was reachable.
Time O(n)Space O(1)
def can_jump(nums: list[int]) -> bool:
"""Whether you can reach the last index from index 0, where nums[i] is the
longest jump you can make from index i."""
farthest = 0 # Every index up to here is reachable.
for i, jump in enumerate(nums):
if i > farthest:
return False # Nothing before i can reach it.
farthest = max(farthest, i + jump)
return True
if __name__ == "__main__":
print(can_jump([2, 3, 1, 1, 4])) # True
print(can_jump([3, 2, 1, 0, 4])) # False
print(can_jump([0])) # True// Whether you can reach the last index from index 0, where nums[i] is the
// longest jump you can make from index i.
export function canJump(nums: number[]): boolean {
let farthest = 0; // Every index up to here is reachable.
for (let i = 0; i < nums.length; i++) {
if (i > farthest) return false; // Nothing before i can reach it.
farthest = Math.max(farthest, i + nums[i]);
}
return true;
}
console.log(canJump([2, 3, 1, 1, 4])); // true
console.log(canJump([3, 2, 1, 0, 4])); // false
console.log(canJump([0])); // truepackage main
import "fmt"
// canJump reports whether you can reach the last index from index 0, where
// nums[i] is the longest jump you can make from index i.
func canJump(nums []int) bool {
farthest := 0 // Every index up to here is reachable.
for i, jump := range nums {
if i > farthest {
return false // Nothing before i can reach it.
}
farthest = max(farthest, i+jump)
}
return true
}
func main() {
fmt.Println(canJump([]int{2, 3, 1, 1, 4})) // true
fmt.Println(canJump([]int{3, 2, 1, 0, 4})) // false
fmt.Println(canJump([]int{0})) // true
}using System;
public class Solution
{
// Whether you can reach the last index from index 0, where nums[i] is the
// longest jump you can make from index i.
public static bool CanJump(int[] nums)
{
int farthest = 0; // Every index up to here is reachable.
for (int i = 0; i < nums.Length; i++)
{
if (i > farthest) return false; // Nothing before i can reach it.
farthest = Math.Max(farthest, i + nums[i]);
}
return true;
}
public static void Main()
{
Console.WriteLine(CanJump(new[] { 2, 3, 1, 1, 4 })); // True
Console.WriteLine(CanJump(new[] { 3, 2, 1, 0, 4 })); // False
Console.WriteLine(CanJump(new[] { 0 })); // True
}
}public class Solution {
// Whether you can reach the last index from index 0, where nums[i] is the
// longest jump you can make from index i.
static boolean canJump(int[] nums) {
int farthest = 0; // Every index up to here is reachable.
for (int i = 0; i < nums.length; i++) {
if (i > farthest) return false; // Nothing before i can reach it.
farthest = Math.max(farthest, i + nums[i]);
}
return true;
}
public static void main(String[] args) {
System.out.println(canJump(new int[] {2, 3, 1, 1, 4})); // true
System.out.println(canJump(new int[] {3, 2, 1, 0, 4})); // false
System.out.println(canJump(new int[] {0})); // true
}
}Worked Problems
Each solution stays folded until you open it, so you can try the problem first. Every one is a complete program in Python, TypeScript, Go, C# and Java, run and checked against its expected output.
Problem
Same setup as Jump Game: nums[i] is the longest jump you can make from index i, and you start at index 0. This time the last index is always reachable. Return the fewest jumps it takes to get there.
Example: [2, 3, 1, 1, 4] returns 2. Jump 1 step to index 1, then 3 steps to the last index.
Show SolutionHide Solution
Approach
Think of it as a breadth-first search over ranges of indices. With 0 jumps you can only stand at index 0. With 1 jump you can reach any index up to nums[0]. With each further jump, you can reach anything up to the farthest point the previous range can jump to. Count ranges until one covers the last index.
index 0 1 2 3 4 nums 2 3 1 1 4 0 jumps 0 1 jump 1 2 2 jumps 3 4
In the code, currentEnd is the end of the current range and farthest is the end of the next one, built up as you scan. When i reaches currentEnd, you've seen everything this many jumps can reach, so you take another jump and the next range begins. The loop stops before the last index, because standing on it means you've arrived. If it ran one step further, it would count an extra jump whenever a range ends exactly on the last index.
Time O(n)Space O(1)
def min_jumps(nums: list[int]) -> int:
"""Fewest jumps from index 0 to the last index, where nums[i] is the longest
jump you can make from index i. Assumes the last index is reachable."""
jumps = 0
current_end = 0 # Farthest index reachable with `jumps` jumps.
farthest = 0 # Farthest index reachable with one more.
# Stop before the last index: once you stand on it, you're done jumping.
for i in range(len(nums) - 1):
farthest = max(farthest, i + nums[i])
if i == current_end:
# You've seen every index this many jumps can reach. Take another.
jumps += 1
current_end = farthest
return jumps
if __name__ == "__main__":
print(min_jumps([2, 3, 1, 1, 4])) # 2
print(min_jumps([1, 1, 1, 1])) # 3
print(min_jumps([0])) # 0// Fewest jumps from index 0 to the last index, where nums[i] is the longest
// jump you can make from index i. Assumes the last index is reachable.
export function minJumps(nums: number[]): number {
let jumps = 0;
let currentEnd = 0; // Farthest index reachable with `jumps` jumps.
let farthest = 0; // Farthest index reachable with one more.
// Stop before the last index: once you stand on it, you're done jumping.
for (let i = 0; i < nums.length - 1; i++) {
farthest = Math.max(farthest, i + nums[i]);
if (i === currentEnd) {
// You've seen every index this many jumps can reach. Take another.
jumps++;
currentEnd = farthest;
}
}
return jumps;
}
console.log(minJumps([2, 3, 1, 1, 4])); // 2
console.log(minJumps([1, 1, 1, 1])); // 3
console.log(minJumps([0])); // 0package main
import "fmt"
// minJumps returns the fewest jumps from index 0 to the last index, where
// nums[i] is the longest jump you can make from index i. It assumes the last
// index is reachable.
func minJumps(nums []int) int {
jumps := 0
currentEnd := 0 // Farthest index reachable with `jumps` jumps.
farthest := 0 // Farthest index reachable with one more.
// Stop before the last index: once you stand on it, you're done jumping.
for i := 0; i < len(nums)-1; i++ {
farthest = max(farthest, i+nums[i])
if i == currentEnd {
// You've seen every index this many jumps can reach. Take another.
jumps++
currentEnd = farthest
}
}
return jumps
}
func main() {
fmt.Println(minJumps([]int{2, 3, 1, 1, 4})) // 2
fmt.Println(minJumps([]int{1, 1, 1, 1})) // 3
fmt.Println(minJumps([]int{0})) // 0
}using System;
public class Solution
{
// Fewest jumps from index 0 to the last index, where nums[i] is the longest
// jump you can make from index i. Assumes the last index is reachable.
public static int MinJumps(int[] nums)
{
int jumps = 0;
int currentEnd = 0; // Farthest index reachable with `jumps` jumps.
int farthest = 0; // Farthest index reachable with one more.
// Stop before the last index: once you stand on it, you're done jumping.
for (int i = 0; i < nums.Length - 1; i++)
{
farthest = Math.Max(farthest, i + nums[i]);
if (i == currentEnd)
{
// You've seen every index this many jumps can reach. Take another.
jumps++;
currentEnd = farthest;
}
}
return jumps;
}
public static void Main()
{
Console.WriteLine(MinJumps(new[] { 2, 3, 1, 1, 4 })); // 2
Console.WriteLine(MinJumps(new[] { 1, 1, 1, 1 })); // 3
Console.WriteLine(MinJumps(new[] { 0 })); // 0
}
}public class Solution {
// Fewest jumps from index 0 to the last index, where nums[i] is the longest
// jump you can make from index i. Assumes the last index is reachable.
static int minJumps(int[] nums) {
int jumps = 0;
int currentEnd = 0; // Farthest index reachable with `jumps` jumps.
int farthest = 0; // Farthest index reachable with one more.
// Stop before the last index: once you stand on it, you're done jumping.
for (int i = 0; i < nums.length - 1; i++) {
farthest = Math.max(farthest, i + nums[i]);
if (i == currentEnd) {
// You've seen every index this many jumps can reach. Take another.
jumps++;
currentEnd = farthest;
}
}
return jumps;
}
public static void main(String[] args) {
System.out.println(minJumps(new int[] {2, 3, 1, 1, 4})); // 2
System.out.println(minJumps(new int[] {1, 1, 1, 1})); // 3
System.out.println(minJumps(new int[] {0})); // 0
}
}Problem
Gas stations sit on a circular road. Station i has gas[i] fuel to pick up, and driving from station i to the next one burns cost[i]. Your tank starts empty and has no limit. Return the station to start from so you can drive all the way around once, or -1 if no start works. When an answer exists, it's unique.
Example: gas = [1, 2, 3, 4, 5] and cost = [3, 4, 5, 1, 2] returns 3. Starting at station 3, you pick up 4 and burn 1, leaving 3, and the tank never goes negative on the way around.
Show SolutionHide Solution
Approach
Trying every start and simulating the loop is O(n²). Two observations bring it down to one pass:
- If the total gas is less than the total cost, no start can work, because the loop as a whole burns more than it gets. Otherwise some start does work, and the next point finds it.
- Drive from
startand keep a runningtank. If it drops below zero on the way to stationi + 1, thenstartfails, and so does every station betweenstartandi. You reached each of them with zero or more fuel, so starting there with an empty tank can only leave you worse off. Skip them all and tryi + 1.
station 0 1 2 3 4 gas - cost -2 -2 -2 +3 +3 tank -2 -2 -2 3 6 start 1 2 3 3 3
Why does the last candidate work? The stretch from it to the end of the array never ran dry, and every stretch before it ended in a deficit. A total of zero or more means the fuel you carry past the end is enough to cover those deficits as you wrap around.
Time O(n)Space O(1)
def starting_station(gas: list[int], cost: list[int]) -> int:
"""The station to start from to drive once around the loop, or -1 if none
works. gas[i] is the fuel at station i, and cost[i] is the fuel it takes to
drive from station i to the next one."""
total = 0 # Net fuel over the whole loop.
tank = 0 # Fuel left since leaving start.
start = 0
for i in range(len(gas)):
total += gas[i] - cost[i]
tank += gas[i] - cost[i]
if tank < 0:
# Stuck before station i + 1. Every station from start to i fails too,
# since you reached each one with zero or more fuel. Try i + 1 next.
start = i + 1
tank = 0
return start if total >= 0 else -1 # A non-negative total means start works.
if __name__ == "__main__":
print(starting_station([1, 2, 3, 4, 5], [3, 4, 5, 1, 2])) # 3
print(starting_station([2, 3, 4], [3, 4, 3])) # -1
print(starting_station([3, 1, 1], [1, 2, 2])) # 0// The station to start from to drive once around the loop, or -1 if none
// works. gas[i] is the fuel at station i, and cost[i] is the fuel it takes to
// drive from station i to the next one.
export function startingStation(gas: number[], cost: number[]): number {
let total = 0; // Net fuel over the whole loop.
let tank = 0; // Fuel left since leaving start.
let start = 0;
for (let i = 0; i < gas.length; i++) {
total += gas[i] - cost[i];
tank += gas[i] - cost[i];
if (tank < 0) {
// Stuck before station i + 1. Every station from start to i fails too,
// since you reached each one with zero or more fuel. Try i + 1 next.
start = i + 1;
tank = 0;
}
}
return total >= 0 ? start : -1; // A non-negative total means start works.
}
console.log(startingStation([1, 2, 3, 4, 5], [3, 4, 5, 1, 2])); // 3
console.log(startingStation([2, 3, 4], [3, 4, 3])); // -1
console.log(startingStation([3, 1, 1], [1, 2, 2])); // 0package main
import "fmt"
// startingStation returns the station to start from to drive once around the
// loop, or -1 if none works. gas[i] is the fuel at station i, and cost[i] is
// the fuel it takes to drive from station i to the next one.
func startingStation(gas, cost []int) int {
total := 0 // Net fuel over the whole loop.
tank := 0 // Fuel left since leaving start.
start := 0
for i := range gas {
total += gas[i] - cost[i]
tank += gas[i] - cost[i]
if tank < 0 {
// Stuck before station i + 1. Every station from start to i fails too,
// since you reached each one with zero or more fuel. Try i + 1 next.
start = i + 1
tank = 0
}
}
if total < 0 {
return -1
}
return start // A non-negative total means start works.
}
func main() {
fmt.Println(startingStation([]int{1, 2, 3, 4, 5}, []int{3, 4, 5, 1, 2})) // 3
fmt.Println(startingStation([]int{2, 3, 4}, []int{3, 4, 3})) // -1
fmt.Println(startingStation([]int{3, 1, 1}, []int{1, 2, 2})) // 0
}using System;
public class Solution
{
// The station to start from to drive once around the loop, or -1 if none
// works. gas[i] is the fuel at station i, and cost[i] is the fuel it takes to
// drive from station i to the next one.
public static int StartingStation(int[] gas, int[] cost)
{
int total = 0; // Net fuel over the whole loop.
int tank = 0; // Fuel left since leaving start.
int start = 0;
for (int i = 0; i < gas.Length; i++)
{
total += gas[i] - cost[i];
tank += gas[i] - cost[i];
if (tank < 0)
{
// Stuck before station i + 1. Every station from start to i fails too,
// since you reached each one with zero or more fuel. Try i + 1 next.
start = i + 1;
tank = 0;
}
}
return total >= 0 ? start : -1; // A non-negative total means start works.
}
public static void Main()
{
Console.WriteLine(StartingStation(new[] { 1, 2, 3, 4, 5 }, new[] { 3, 4, 5, 1, 2 })); // 3
Console.WriteLine(StartingStation(new[] { 2, 3, 4 }, new[] { 3, 4, 3 })); // -1
Console.WriteLine(StartingStation(new[] { 3, 1, 1 }, new[] { 1, 2, 2 })); // 0
}
}public class Solution {
// The station to start from to drive once around the loop, or -1 if none
// works. gas[i] is the fuel at station i, and cost[i] is the fuel it takes to
// drive from station i to the next one.
static int startingStation(int[] gas, int[] cost) {
int total = 0; // Net fuel over the whole loop.
int tank = 0; // Fuel left since leaving start.
int start = 0;
for (int i = 0; i < gas.length; i++) {
total += gas[i] - cost[i];
tank += gas[i] - cost[i];
if (tank < 0) {
// Stuck before station i + 1. Every station from start to i fails too,
// since you reached each one with zero or more fuel. Try i + 1 next.
start = i + 1;
tank = 0;
}
}
return total >= 0 ? start : -1; // A non-negative total means start works.
}
public static void main(String[] args) {
System.out.println(startingStation(new int[] {1, 2, 3, 4, 5}, new int[] {3, 4, 5, 1, 2})); // 3
System.out.println(startingStation(new int[] {2, 3, 4}, new int[] {3, 4, 3})); // -1
System.out.println(startingStation(new int[] {3, 1, 1}, new int[] {1, 2, 2})); // 0
}
}Common Pitfalls
- Trusting a greedy rule because it passed the examples. Try small inputs where the best move now blocks a better one later, as coins
[1, 3, 4]with amount6do. - Sorting by the wrong key. To keep the most non-overlapping intervals, sort by end time. Sorting by start or by length can lock in a bad pick early.
- Assuming greedy means taking the biggest value. The safe choice is whatever an exchange argument supports, like the earliest end time, and often it isn't the largest anything.
- In Jump Game II, looping all the way to the last index. If a range ends exactly there, you count a jump you never take.
- In Gas Station, retrying from
start + 1after running dry instead of jumping toi + 1. It still finds the answer, but it rechecks stations already ruled out and becomes O(n²).
Practice
More problems to try on your own.