Greedy
Loading…
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.
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.
Reach for a greedy approach when you see these:
Take Jump Game on nums = [2, 3, 1, 1, 4], where nums[i] is the longest jump from index i:
farthest, the furthest index we can reach so far. It starts at 0.0, jump length 2 reaches index 2, so farthest = 2.1, we are inside what we can reach, and 1 + 3 = 4, so farthest = 4.4 is the last index and farthest ≥ 4: we can reach the end. Return true.[3, 2, 1, 0, 4], farthest stops at 3. At index 4 we have i > farthest, a gap we cannot cross. Return false.We never tried every path; one running value summarised everything the next step needs.
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.
Jump Game — track the farthest reachable index. If you ever stand past it, you are stuck:
// CanJump (LeetCode 55): 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 (LeetCode 45): 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 (LeetCode 455): 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 (LeetCode 763): 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 (LeetCode 134): 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.
| 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.
Ordered Easy → Hard. For each, say aloud what the local choice is and why it can never hurt.
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?