Greedy
One-liner: at every step take the choice that looks best right now, and never come back to change it — which is only allowed when you can argue that the best local choice can never hurt the final answer.
The analogy
You are packing a suitcase for a trip and you must leave in five minutes. You grab the most useful thing first, then the next most useful, until the case is full. You never unpack and rethink. That works when "most useful" really is the right order. It fails when two small items together beat one big one — and then you need to compare combinations, which is dynamic programming. Greedy is the fast path; the work is proving it is safe.
Recognition signals
Reach for a greedy approach when you see these:
- You want a maximum, minimum or yes/no answer, not a list of all answers.
- There is a natural order to the choices (earliest finish, smallest first, farthest reach) and a choice made early does not block a better one later.
- A small instance can be argued by hand: "if I did anything else here, I could swap it for my greedy choice and be no worse off" (the exchange argument).
- The obvious brute force tries many combinations, and a sort or a single running value would replace it.
Step-by-step walkthrough
Take Jump Game on nums = [2, 3, 1, 1, 4], where nums[i] is the longest jump from index i:
- Keep one number:
farthest, the furthest index we can reach so far. It starts at0. - At index
0, jump length2reaches index2, sofarthest = 2. - At index
1, we are inside what we can reach, and1 + 3 = 4, sofarthest = 4. - Index
4is the last index andfarthest ≥ 4: we can reach the end. Returntrue. - For
[3, 2, 1, 0, 4],fartheststops at3. At index4we havei > farthest, a gap we cannot cross. Returnfalse.
We never tried every path; one running value summarised everything the next step needs.
Code template
Order the choices if the problem needs it, keep one running value, and commit to each safe choice for good. The two marked spots change per problem.
// Template: make the locally best choice, never revisit it, and keep only the state the next choice needs.
func greedyTemplate(items []int) int {
slices.Sort(items) // 1. order the choices so the "best" one is always next (not every problem needs this)
best := 0 // 2. the one running value the next decision depends on
for _, x := range items {
if true /* 3. is taking x safe, given only best? */ {
best += x // 4. commit to it; we never undo this
}
}
return best
}Why each part exists:
slices.Sort(items)Greedy needs the "best next choice" to be obvious. Sorting makes it the next element. Some problems skip the sort because the input order already is the order (Jump Game); others sort by the right key (end time, size, greed).
best := 0The single piece of state the next decision depends on: furthest reach, the last kept end, the current tank. If you need a table of past states, it is probably DP.
if true /* is taking x safe? */The heart of the problem. Write the condition in words first, and be able to say why a different choice here could not do better.
best += xCommit. A greedy algorithm never undoes a choice. If you feel the urge to undo, you are describing backtracking or DP.
The real solutions
Jump Game — track the farthest reachable index. If you ever stand past it, you are stuck:
// CanJump: can you reach the last index if nums[i] is the max jump length from i?
// Greedy: track the farthest index reachable so far. If we ever stand beyond it, we are stuck.
func CanJump(nums []int) bool {
farthest := 0
for i, jump := range nums {
if i > farthest { // an unreachable gap
return false
}
farthest = max(farthest, i+jump)
}
return true
}Jump Game II (fewest jumps) — the same reach idea, but count a jump each time you hit the edge of the current jump. end is that edge and farthest is the best edge of the next jump:
// MinJumps: fewest jumps to reach the last index (it is always reachable).
// Greedy BFS-by-ranges: end is the edge of the current jump; farthest is the best edge of the next one.
func MinJumps(nums []int) int {
jumps, end, farthest := 0, 0, 0
for i := 0; i < len(nums)-1; i++ {
farthest = max(farthest, i+nums[i])
if i == end { // we must jump now: take the best landing we saw
jumps++
end = farthest
}
}
return jumps
}Assign Cookies — serve the least greedy child with the smallest cookie that satisfies them. Wasting a big cookie on a small appetite can only hurt:
// FindContentChildren: each child needs a cookie of size >= their greed; one cookie per child.
// Greedy: serve the least greedy child with the smallest cookie that satisfies them.
func FindContentChildren(greed, cookies []int) int {
g, s := slices.Clone(greed), slices.Clone(cookies)
slices.Sort(g)
slices.Sort(s)
child := 0
for _, cookie := range s {
if child < len(g) && cookie >= g[child] {
child++ // this cookie satisfies the next-least-greedy child
}
}
return child
}Partition Labels — a part must stretch to the last occurrence of every letter it contains; close it exactly when the scan reaches that edge:
// PartitionLabels: split s so each letter appears in at most one part; return the part sizes.
// Greedy: a part must reach at least the last occurrence of every letter inside it.
func PartitionLabels(s string) []int {
last := map[rune]int{}
for i, r := range s {
last[r] = i
}
var sizes []int
start, end := 0, 0
for i, r := range s {
end = max(end, last[r]) // extend the part to cover this letter's last occurrence
if i == end { // nothing inside the part appears later: close it
sizes = append(sizes, end-start+1)
start = i + 1
}
}
return sizes
}Gas Station — if the tank goes negative at i, no start between the old start and i can work, so restart after i. If total gas is below total cost, no start works at all:
// CanCompleteCircuit: the start index that completes the circular route, or -1.
// Greedy: if the tank goes negative at i, no start in [start, i] can work, so restart at i+1.
func CanCompleteCircuit(gas, cost []int) int {
total, tank, start := 0, 0, 0
for i := range gas {
diff := gas[i] - cost[i]
total += diff
tank += diff
if tank < 0 {
start = i + 1
tank = 0
}
}
if total < 0 {
return -1
}
return start
}Looking for interval greedy (earliest end first, minimum arrows)? That is on the Intervals page, built on the same idea.
Complexity
| Approach | Time | Space |
|---|---|---|
| Try every combination (brute force) | O(2ⁿ) or worse | O(n) |
| Greedy with a sort | O(n log n) | O(1) – O(n) |
| Greedy single pass (jump game, gas station) | O(n) | O(1) |
When there is a sort it dominates; the greedy pass itself is linear.
Common mistakes
Practice ladder
Ordered Easy → Hard. For each, say aloud what the local choice is and why it can never hurt.
- 1.Assign CookiesEasyWho should get the smallest cookie that still works?
- 2.Best Time to Buy and Sell Stock IIMediumCan you collect every upward step?
- 3.Jump GameMediumOne number: how far can you get?
- 4.Gas StationMediumIf the tank dies at i, what does that say about earlier starts?
- 5.Partition LabelsMedium
- 6.Jump Game IIMediumCount the edges of the reachable range, not the paths.
- 7.CandyHardTwo passes: satisfy the left neighbour, then the right.
Which pattern? Drills
Unlabeled problems — pick the pattern, then read why.
Each cell of an array holds the longest jump you can make from that cell. Starting at the first cell, decide whether you can land on the last one.
Which pattern?