LeetCode #45 — Medium

Jump Game II

A complete visual walkthrough of the greedy BFS approach — from intuition to a clean O(n) solution.

Solve on LeetCode
The Problem

Problem Statement

You are given a 0-indexed array of integers nums of length n. You are initially positioned at index 0.

Each element nums[i] represents the maximum length of a forward jump from index i. In other words, if you are at index i, you can jump to any index (i + j) where:

  • 0 <= j <= nums[i] and
  • i + j < n

Return the minimum number of jumps to reach index n - 1. The test cases are generated such that you can reach index n - 1.

Example 1:

Input: nums = [2,3,1,1,4]
Output: 2
Explanation: The minimum number of jumps to reach the last index is 2. Jump 1 step from index 0 to 1, then 3 steps to the last index.

Example 2:

Input: nums = [2,3,0,1,4]
Output: 2

Constraints:

  • 1 <= nums.length <= 104
  • 0 <= nums[i] <= 1000
  • It's guaranteed that you can reach nums[n - 1].
Patterns Used

Roadmap

  1. The Brute Force — BFS / DP
  2. The Core Insight — Greedy Implicit BFS
  3. Walkthrough — Jump Levels for [2,3,1,1,4]
  4. Edge Cases
  5. Full Annotated Code
  6. Interactive Demo
  7. Complexity Analysis
Step 01

The Brute Force — BFS / DP

The straightforward approach treats this as a shortest path problem. From each index, you can jump to indices [i+1, i+nums[i]]. Use BFS to find the minimum jumps to reach the end.

BFS Approach

For [2, 3, 1, 1, 4], the BFS explores level by level:

Level 0: index 0 (value=2)
Level 1: indices 1, 2 (reachable in 1 jump)
Level 2: indices 3, 4 (reachable in 2 jumps) — index 4 is the end!

This works but BFS with a queue uses O(n) space and has overhead from queue operations.

💡

The BFS levels are contiguous ranges! Because we can jump to any index from i+1 to i+nums[i], each BFS level is a contiguous subarray. We don't need a queue at all — just track the range boundaries.

Step 02

The Core Insight — Greedy Implicit BFS

Instead of maintaining a queue, we track two boundaries: curEnd (the farthest index reachable in the current number of jumps) and farthest (the farthest index reachable from any position in the current level).

Three Variables, One Pass

jumps
Number of jumps taken so far
curEnd
End of current BFS level
farthest
Max reachable from this level

As we scan left to right, we update farthest = max(farthest, i + nums[i]). When i reaches curEnd, we've exhausted the current level — increment jumps and set curEnd = farthest.

Why greedy works here: At each BFS level, we want to reach as far as possible. Among all positions reachable in k jumps, the best next jump is the one that maximizes the new farthest point. Since we scan all positions in the level, we naturally find this maximum.

Step 03

Walkthrough — Jump Levels

Let's trace through [2, 3, 1, 1, 4] showing how the three variables evolve.

Array with Jump Ranges

2
i=0
3
i=1
1
i=2
1
i=3
4
i=4
lvl 0
lvl 1
lvl 2

Step-by-Step Trace

i=0: nums[0]=2
farthest = max(0, 0+2) = 2. i == curEnd(0), so jumps=1, curEnd=2.
i=1: nums[1]=3
farthest = max(2, 1+3) = 4. i != curEnd, continue.
i=2: nums[2]=1
farthest = max(4, 2+1) = 4. i == curEnd(2), so jumps=2, curEnd=4.
i=3: nums[3]=1
farthest = max(4, 3+1) = 4. i != curEnd, continue. Loop ends (i < n-1).
Answer: 2 jumps (0 → 1 → 4)
Step 04

Edge Cases

Single element
[0] → 0 jumps — already at the end. The loop condition i < nums.length - 1 ensures we don't process it.
Direct jump to end
[5,1,1,1,1] → 1 jump — from index 0 you can reach the end directly. The first level boundary at i=0 sets curEnd=4, and the loop ends.
All ones
[1,1,1,1] → 3 jumps — each BFS level contains exactly one index, so we need n-1 jumps. Worst case for this algorithm.
Step 05

Full Annotated Code

class Solution {
    public int jump(int[] nums) {

        int jumps = 0;        // number of jumps taken
        int curEnd = 0;      // end boundary of current BFS level
        int farthest = 0;    // farthest reachable from current level

        // Don't process the last index — we just need to reach it
        for (int i = 0; i < nums.length - 1; i++) {

            // Update the farthest we can reach
            farthest = Math.max(farthest, i + nums[i]);

            // Reached the end of current level?
            if (i == curEnd) {
                jumps++;               // take a jump
                curEnd = farthest;     // expand to next level
            }
        }

        return jumps;
    }
}
Step 06

Interactive Demo

Enter an array and watch the greedy BFS scan through, tracking jump levels and boundaries.

Jump Level Visualizer


Step 07

Complexity Analysis

Time
O(n)
Space
O(1)

We scan the array exactly once, doing O(1) work per index. No extra data structures -- just three integer variables (jumps, curEnd, farthest). This is optimal since we must read each element at least once.

Approach Breakdown

BRUTE FORCE
O(2n) time
O(n) space

From each index, recursively try every reachable next index. Each position can branch into up to n children, and without memoization the recursion tree grows exponentially. Stack depth is at most n.

BFS / DP
O(n2) time
O(n) space

BFS explores indices level by level using a queue. Each index is dequeued once, but expanding it may enqueue up to n neighbors in the worst case (e.g., [n,n,n,...]). The queue stores at most n entries, giving O(n) space.

OPTIMAL
O(n) time
O(1) space

A single left-to-right pass tracks two boundaries (curEnd, farthest) using O(1) work per index. Because BFS levels form contiguous ranges, no queue is needed -- just three integer variables replace the entire data structure.

🎯

Greedy works because we always extend to the farthest reachable position at each "level." BFS levels form contiguous ranges, so we replace the queue with two pointers. This pattern appears in many "minimum steps" problems -- whenever levels are contiguous, greedy beats BFS.

Coach Notes

Common Mistakes

Review these before coding to avoid predictable interview regressions.

State misses one required dimension

Wrong move: An incomplete state merges distinct subproblems and caches incorrect answers.

Usually fails on: Correctness breaks on cases that differ only in hidden state.

Fix: Define state so each unique subproblem maps to one DP cell.

Off-by-one on range boundaries

Wrong move: Loop endpoints miss first/last candidate.

Usually fails on: Fails on minimal arrays and exact-boundary answers.

Fix: Re-derive loops from inclusive/exclusive ranges before coding.

Using greedy without proof

Wrong move: Locally optimal choices may fail globally.

Usually fails on: Counterexamples appear on crafted input orderings.

Fix: Verify with exchange argument or monotonic objective before committing.