The best choice right now
Paying 0.80 EUR in coins, you probably take the biggest coin that fits (50 cents), then the next biggest (20, then 10), without thinking about it. Each step makes the best choice at that moment, and with euro coins, you end up with the fewest coins. That’s a greedy algorithm: always take what looks best now, and never undo it.
It’s fast because there’s nothing to try and nothing to undo. The catch is that “best right now” isn’t always “best overall”, and the second half of this lesson is about telling which is which.
Greedy makes an irrevocable locally optimal choice at each step and reduces the problem to a smaller one of the same kind. It’s correct when the problem has the greedy-choice property (some optimal solution starts with the greedy choice) and optimal substructure (what remains after that choice is solved optimally by the same rule). In interviews, the code is usually trivial; the evaluation is whether you can state the invariant and defend it, or produce a counterexample when it fails.
Jump Game
Jump Game.
nums[i]is the maximum jump length from indexi. Starting at index 0, can you reach the last index?[2, 3, 1, 1, 4]→ true.[3, 2, 1, 0, 4]→ false (every path lands on the 0).
Trying every path is backtracking, exponential. But you don’t need the path, only how far you could possibly get. Walk left to right and keep the furthest index reachable so far:
func canJump(_ nums: [Int]) -> Bool {
var furthest = 0 // furthest index reachable so far
for i in 0..<nums.count {
if i > furthest { return false } // this index can't be reached
furthest = max(furthest, i + nums[i])
}
return true
}fun canJump(nums: IntArray): Boolean {
var furthest = 0 // furthest index reachable so far
for (i in nums.indices) {
if (i > furthest) return false // this index can't be reached
furthest = maxOf(furthest, i + nums[i])
}
return true
}boolean canJump(int[] nums) {
int furthest = 0; // furthest index reachable so far
for (int i = 0; i < nums.length; i++) {
if (i > furthest) return false; // this index can't be reached
furthest = Math.max(furthest, i + nums[i]);
}
return true;
}function canJump(nums: number[]): boolean {
let furthest = 0; // furthest index reachable so far
for (let i = 0; i < nums.length; i++) {
if (i > furthest) return false; // this index can't be reached
furthest = Math.max(furthest, i + nums[i]);
}
return true;
}One number, furthest, replaces all the paths. Every index up to furthest can be reached somehow, and from each of them you might jump further. If the loop ever arrives at an index beyond furthest, there’s a gap nobody can cross. O(n) time, O(1) space.
Invariant: after processing index i, furthest is the maximum index reachable using jumps from indices 0…i. Every index ≤ furthest is reachable (reachability is contiguous: you can always jump shorter), so the first i > furthest proves the end is unreachable. Note the greedy isn’t “take the longest jump”; it’s “track the best frontier”, which is the more common shape of greedy in interviews.
Practice: trace it, then break it
[2, 3, 1, 1, 4], furthest starts at 0.i = 0: 0 ≤ 0, furthest = max(0, 0 + 2) = 2.i = 1: 1 ≤ 2, furthest = max(2, 1 + 3) = 4. That’s already the last index.i = 2, 3, 4: all ≤ 4. The loop ends: true.[3, 2, 1, 0, 4]: furthest becomes 3, 3, 3, 3 at i = 0…3 (each jump lands on index 3, the 0). Ati = 4, 4 > 3: false.
Here’s a version someone wrote from memory. It returns true for [3, 2, 1, 0, 4]:
Tap the line with the bug.
func canJump(_ nums: [Int]) -> Bool {
var furthest = 0
for i in 0..<nums.count {
furthest = max(furthest, i + nums[i])
}
return furthest >= nums.count - 1
}fun canJump(nums: IntArray): Boolean {
var furthest = 0
for (i in nums.indices) {
furthest = maxOf(furthest, i + nums[i])
}
return furthest >= nums.size - 1
}boolean canJump(int[] nums) {
int furthest = 0;
for (int i = 0; i < nums.length; i++) {
furthest = Math.max(furthest, i + nums[i]);
}
return furthest >= nums.length - 1;
}function canJump(nums: number[]): boolean {
let furthest = 0;
for (let i = 0; i < nums.length; i++) {
furthest = Math.max(furthest, i + nums[i]);
}
return furthest >= nums.length - 1;
}Hint: At i = 4, furthest is 3. Can you actually stand on index 4 and jump from there?
The loop uses nums[i] from every index, including ones that can’t be reached. At i = 4, the 4 is beyond furthest = 3, but the code still adds its jump: furthest becomes 8 and the function says true. Only reachable indices may extend the frontier, so check first:
if i > furthest { return false }
furthest = max(furthest, i + nums[i])if (i > furthest) return false
furthest = maxOf(furthest, i + nums[i])if (i > furthest) return false;
furthest = Math.max(furthest, i + nums[i]);if (i > furthest) return false;
furthest = Math.max(furthest, i + nums[i]);Jump Game II
Same input, and the last index is always reachable. Return the minimum number of jumps to reach it. [2, 3, 1, 1, 4] → 2 (0 → 1 → 4).
›
Think of it as BFS without a queue: all indices reachable with k jumps form a contiguous range. The next range ends at the furthest index reachable from anywhere in the current one.
SolutionTry it yourself firstTime O(n)Space O(1)
func jump(_ nums: [Int]) -> Int {
var jumps = 0, currentEnd = 0, furthest = 0
for i in 0..<(nums.count - 1) { // never jump from the last index
furthest = max(furthest, i + nums[i])
if i == currentEnd { // the current range is used up
jumps += 1
currentEnd = furthest
}
}
return jumps
}fun jump(nums: IntArray): Int {
var jumps = 0; var currentEnd = 0; var furthest = 0
for (i in 0 until nums.size - 1) { // never jump from the last index
furthest = maxOf(furthest, i + nums[i])
if (i == currentEnd) { // the current range is used up
jumps++
currentEnd = furthest
}
}
return jumps
}int jump(int[] nums) {
int jumps = 0, currentEnd = 0, furthest = 0;
for (int i = 0; i < nums.length - 1; i++) { // never jump from the last index
furthest = Math.max(furthest, i + nums[i]);
if (i == currentEnd) { // the current range is used up
jumps++;
currentEnd = furthest;
}
}
return jumps;
}function jump(nums: number[]): number {
let jumps = 0, currentEnd = 0, furthest = 0;
for (let i = 0; i < nums.length - 1; i++) { // never jump from the last index
furthest = Math.max(furthest, i + nums[i]);
if (i === currentEnd) { // the current range is used up
jumps++;
currentEnd = furthest;
}
}
return jumps;
}Each range [previousEnd + 1, currentEnd] is one BFS level. The loop stops before the last index, otherwise reaching it exactly at a range boundary would count one jump too many. Saying “this is level-order BFS on an implicit graph, compressed to O(1) space” is the explanation interviewers want; the DP alternative is O(n²).
- Greedy = irrevocable local choice + same-shaped remaining problem. Correct only with the greedy-choice property and optimal substructure; be ready to argue it.
- Common shape: track one frontier value (furthest reach, minimum so far, running balance) in one pass.
- Jump Game:
if i > furthest return false; furthest = max(furthest, i + nums[i]). Jump Game II: BFS levels as ranges, count wheni == currentEnd, loop ton − 2.
Check this part
What does a greedy algorithm do at each step?
In Jump Game, the loop reaches index i and i > furthest. What does that mean?
Which two properties make a greedy algorithm correct?
Jump Game II in O(n): what is the hidden structure?