Jump Game II
1. Problem & Core Objective
Same array as Jump Game — nums[i] is the maximum jump length from index i — but now return the minimum number of jumps to reach the last index. The input is guaranteed reachable.
nums = [2, 3, 1, 1, 4] → 2 (0 → 1 → 4)
nums = [2, 3, 0, 1, 4] → 2
nums = [1] → 0 (already there)
nums = [2, 4, 1, 1, 4, 2, 1, 4] → 3 (0 → 1 → 5 → 7)Constraints: 1 <= nums.length <= 10^4, 0 <= nums[i] <= 1000, the last index is always reachable.
What's actually being tested: whether you recognise BFS by levels when there's no graph in sight. "Minimum number of moves with uniform cost" is always BFS; the trick is realising the frontier can be tracked with two integers instead of a queue, because reachability is contiguous.
The distractor is a plausible greedy that is simply wrong — and the interviewer is usually waiting to see whether you notice.
2. First-Principles Thought Process
The greedy that fails
The obvious greedy is "each jump should go as far as it can":
0 → 2 → 3 → 4 → 7 on [2, 4, 1, 1, 4, 2, 1, 4] 4 jumpsThe optimal is 3. From index 0 you can reach index 1 or index 2, and index 2 is further — but index 1 holds a 4 and index 2 holds a 1. The best landing spot is not the furthest one; it's the one with the best onward reach.
Measured: the hop-as-far-as-possible rule is wrong on 233 of 4,000 random arrays — about 6%. That's the dangerous failure rate: common enough to be wrong in production, rare enough that a handful of hand-tests won't catch it.
The fix: don't pick a landing spot at all
The insight is that you never have to choose. After one jump you can be at any index in [1, nums[0]]. So instead of committing, ask:
From anywhere in the current reachable window, how far can I get with one more jump?
That's max(i + nums[i]) over the whole window. The next window starts where this one ends. Repeat.
Nobody ever decides where to land — the algorithm reasons about sets of positions, and the answer falls out of how many sets it takes to cover the end.
This is BFS, written without a queue
Level k of a BFS from index 0 is "all indices reachable in exactly k jumps". Because reachability is contiguous (Jump Game §2), level k is always a contiguous range [l, r]. A range needs two integers, not a queue.
| BFS concept | Here |
|---|---|
| frontier / current level | the window [l, r] |
| dequeue the whole level | the for i loop from l to r |
| enqueue neighbours | farthest = max(farthest, i + nums[i]) |
| level counter | jumps |
| goal test | r >= n - 1 |
Naming it as BFS is worth more than any trace — it explains the loop bounds, the counter, and why the answer is optimal, all at once. BFS finds shortest paths in unweighted graphs, and every jump costs 1.
Why the window formulation is optimal
BFS optimality transfers directly: an index first appears in level k exactly when k is the minimum number of jumps to reach it. The window version enumerates levels in order and never skips one, so the first level containing n-1 is the answer.
No exchange argument is needed. This is another case where the "greedy" is really an exact computation wearing a greedy costume.
3. Solution Paths
Approach 1 — Brute force, BFS with a real queue
public int jump(int[] nums) {
int n = nums.length;
int[] dist = new int[n];
Arrays.fill(dist, -1);
dist[0] = 0;
Deque<Integer> q = new ArrayDeque<>();
q.add(0);
while (!q.isEmpty()) {
int i = q.poll();
if (i == n - 1) return dist[i];
for (int j = 1; j <= nums[i] && i + j < n; j++)
if (dist[i + j] < 0) { dist[i + j] = dist[i] + 1; q.add(i + j); }
}
return dist[n - 1];
}- Time
O(n²)· SpaceO(n)
This is the reference implementation — the one I checked the optimal solution against on 4,000 random arrays.
Counter-questions on this approach
⭐ "Why BFS rather than DFS?"
Because the question is a minimum over an unweighted graph. BFS visits nodes in nondecreasing distance order, so the first time an index is dequeued its distance is final. DFS has no such guarantee and would need to explore every path, or be turned into a DP.
That one sentence is the whole justification, and it's the same sentence for word ladders, rotting oranges, and knight moves (16).
"Where does the O(n²) come from if BFS is O(V + E)?"
Eis the problem. Indexihasnums[i]outgoing edges, soE = sum(nums[i]), which isO(n · maxJump)— up to10^7here, andO(n²)when jump lengths scale withn.The greedy version's improvement is not a better search; it's collapsing those edges by noticing they always form a contiguous range.
"Why is the dist[i + j] < 0 check necessary?"
Without it, an index can be enqueued many times — once per predecessor — and the queue grows to
O(E). The check is what makes each index enter the queue exactly once.Note it also encodes the BFS optimality claim: the first assignment is the smallest, so later ones would be strictly worse and are skipped.
Approach 2 — DP over reachable positions
public int jump(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 0; i < n; i++) {
if (dp[i] == Integer.MAX_VALUE) continue; // unreachable — do not relax from it
for (int j = 1; j <= nums[i] && i + j < n; j++)
dp[i + j] = Math.min(dp[i + j], dp[i] + 1);
}
return dp[n - 1];
}- Time
O(n²)· SpaceO(n)
Counter-questions on this approach
⭐ "What breaks if you drop the dp[i] == MAX_VALUE guard?"
Integer.MAX_VALUE + 1overflows toInteger.MIN_VALUE, which then wins everyMath.minit touches. An unreachable index would look like the cheapest thing in the array and poison everything downstream.This is the standard sentinel discipline: a sentinel must survive the arithmetic you perform on it.
MAX_VALUEis safe under comparison and unsafe under+. The alternatives areInteger.MAX_VALUE / 2, or the explicit guard I used — I prefer the guard, because halving a sentinel is a silent assumption about how many times it might be incremented.
"The problem guarantees reachability. Is the guard dead code?"
For the last index, yes — it's guaranteed reachable. For intermediate indices, no:
[2, 0, 0, 1, 4]... actually every index before the end is reachable whenever the end is, since reachability is a prefix. So under the stated guarantee the guard never fires.I'd still write it. The guarantee is a property of the test data, not of the code, and this function is one constraint change away from being wrong without it.
"Is this Bellman-Ford?"
It's one relaxation pass of Bellman-Ford on a DAG, which is enough because the edges all point forwards — processing indices in increasing order means
dp[i]is final before it's used (18). That's the DAG shortest-path algorithm, not the general one.
Approach 3 — Greedy windows, explicit (optimal)
public int jump(int[] nums) {
int jumps = 0, l = 0, r = 0;
while (r < nums.length - 1) {
int farthest = 0;
for (int i = l; i <= r; i++) farthest = Math.max(farthest, i + nums[i]);
l = r + 1;
r = farthest;
jumps++;
}
return jumps;
}- Time
O(n)· SpaceO(1)
Each index is scanned by exactly one window, so the nested loop is linear overall.
Counter-questions on this approach
⭐ "The loops are nested. Justify O(n)."
The windows partition the indices: window
k+1starts atr + 1, exactly where windowkended. No index is ever scanned twice, so the total inner-loop work across all iterations isn.It's the same amortisation argument as a two-pointer scan — nested syntax, linear behaviour.
"What does l actually contribute?"
Correctness of the amortisation, not of the answer. Starting each window at 0 instead of
r + 1would still produce the right count, becausefarthestis monotone — but it would re-scan the prefix every time and beO(n²).Worth stating because it shows you know which line is load-bearing for which property.
"Why while (r < n - 1) rather than r < n?"
r >= n - 1means the last index is inside the current window, so no further jump is needed. Usingr < nwould take one extra jump past the end.
Approach 4 — Greedy windows, one pass (the version to write)
public int jump(int[] nums) {
int jumps = 0, curEnd = 0, farthest = 0;
for (int i = 0; i < nums.length - 1; i++) { // stop BEFORE the last index
farthest = Math.max(farthest, i + nums[i]);
if (i == curEnd) { // current level exhausted
jumps++;
curEnd = farthest;
}
}
return jumps;
}- Time
O(n)· SpaceO(1)
Identical to Approach 3 with the two loops flattened: curEnd is the old r, and reaching it is what closes a level.
Counter-questions on this approach
⭐ "Why i < nums.length - 1 and not i < nums.length?"
Because arriving at the last index means you're done — you must not pay for a jump out of it. If
n - 1happens to equalcurEnd, the full-length loop would incrementjumpsone more time.Concretely on
[2, 3, 1, 1, 4]: the correct answer is 2, and looping ton - 1gives 3. It's the single most common bug in this function.
⭐ "Walk me through why this counts levels correctly."
curEndis the furthest index reachable injumpsjumps. Whilei < curEndwe're still scanning the current level, accumulating how far the next level will reach. Wheni == curEndthe level is fully scanned, so we commit to one more jump and promotefarthestto the new boundary.
jumpstherefore counts level transitions, and BFS guarantees the level index equals the shortest distance.
"Both curEnd and farthest start at 0. Is that an accident?"
No. Before any jump, the reachable set is exactly
{0}, so the level boundary is index 0 and the furthest known reach is 0. Level 0 closes immediately ati = 0, which is correct: one jump is needed as soon asn > 1.For
n == 1the loop never runs and the answer is 0, also correct.
"Does this ever need to check reachability?"
Not under the stated guarantee. Without it,
curEndcould stall —i == curEndfiring whilefarthest == curEndmeans no progress, and you'd return-1. One extra line:if (farthest == curEnd && i < n - 1) return -1;.
4. Why the Optimal Solution Wins
| Approach | Time | Space | Verdict |
|---|---|---|---|
| BFS with a queue | O(n²) | O(n) | Correct reference; edges are the bottleneck |
| Forward DP | O(n²) | O(n) | Same cost, plus a sentinel-overflow hazard |
| Greedy windows, explicit | O(n) | O(1) | Optimal; two loops make the partition visible |
| Greedy, one pass | O(n) | O(1) | Same algorithm, three variables |
O(n) | O(1) | Wrong — 233/4,000 random arrays |
The O(n) versions are BFS with the queue eliminated, which is only possible because each level is a contiguous range. That is the single property doing the work, and it comes from nums[i] being a maximum.
Write Approach 4, explain it as Approach 3. The flattened loop is what you'd ship; the two-loop version is what makes the level structure obvious on a whiteboard.
5. Java Prerequisites
The three-variable level scan
int jumps = 0, curEnd = 0, farthest = 0;
for (int i = 0; i < n - 1; i++) {
farthest = Math.max(farthest, i + nums[i]);
if (i == curEnd) { jumps++; curEnd = farthest; }
}Memorise the shape, not the letters. farthest accumulates, curEnd is the boundary, and the if fires once per level.
Arrays.fill with a sentinel
int[] dp = new int[n];
Arrays.fill(dp, Integer.MAX_VALUE); // int[] defaults to 0, which means "free" here
dp[0] = 0;new int[n] is zero-filled, and 0 is a meaningful value for a minimum — so the fill is mandatory, not stylistic.
Sentinel arithmetic
if (dp[i] == Integer.MAX_VALUE) continue; // guard
dp[i + j] = Math.min(dp[i + j], dp[i] + 1); // MAX_VALUE + 1 wraps to MIN_VALUEArrayDeque as a queue
Deque<Integer> q = new ArrayDeque<>();
q.add(0); // enqueue at the tail
int i = q.poll(); // dequeue from the headPrefer it to LinkedList — same interface, contiguous storage, no node allocation per element (02).
6. Interview Communication Guide
Clarifying questions: Is the end guaranteed reachable (yes — otherwise I'd return −1 on a stalled frontier)? Is nums[i] a maximum or an exact distance (maximum)? Does staying put count as a jump (no)? What's returned for a single-element array (0)?
The pitch
"Minimum number of moves where every move costs the same — that's BFS. The question is what the graph is.
Before writing it, I want to flag the greedy that doesn't work: each jump goes as far as it can. On
[2, 4, 1, 1, 4, 2, 1, 4]that takes 4 jumps and the optimum is 3, because from index 0 the further landing spot — index 2 — has a much worse onward reach than index 1. I measured this rule at about a 6% failure rate on random arrays, which is exactly the frequency that survives casual testing.The fix is to never choose a landing spot. After
kjumps I can be at any index in some window, and becausenums[i]is a maximum, that window is always contiguous. So I ask: from anywhere in the current window, how far does one more jump reach? That maximum is the next window's right edge.That's BFS by levels with the queue replaced by two integers —
curEndis the frontier andfarthestis the next frontier being accumulated.jumpscounts level transitions, and BFS optimality gives the minimum for free.The loop runs to
n - 2, notn - 1. Arriving at the last index means you're finished; scanning it would charge you for a jump out of it. On[2,3,1,1,4]that off-by-one turns 2 into 3.
O(n)time,O(1)space."
Edge cases to volunteer:
| Input | Expected | Tests |
|---|---|---|
[1] | 0 | Loop never runs; must not charge a jump |
[2, 3, 1, 1, 4] | 2 | The n-1 bound — returns 3 if the loop runs to the end |
[2, 4, 1, 1, 4, 2, 1, 4] | 3 | Defeats the hop-as-far-as-possible greedy (gives 4) |
[1, 1, 1, 1] | 3 | Every jump is forced; worst case |
[10000, 1, 1] | 1 | Jump length far exceeds the array |
[2, 0, 2, 0, 1] | 2 | Zeroes inside a window don't break it |
Lead with [2, 4, 1, 1, 4, 2, 1, 4]. Naming the input that kills the naive greedy — before writing any code — is the single most persuasive thing you can do on this problem.
7. Follow-Up Questions — Modified Constraints
⭐ "Return the actual sequence of indices, not the count."
Inside each level scan, remember which
iproduced thefarthestvalue. That gives one optimal landing index per level, and chaining them reconstructs a path.Unlike most path-reconstruction follow-ups this stays
O(1)extra per level andO(jumps)total — because a level is summarised by a single winner rather than a full predecessor array.
⭐ "What if the end isn't guaranteed reachable?"
Detect a stalled frontier: if
i == curEndfires andfarthest == curEnd, the next window is empty and nothing beyond is reachable — return −1. That's the Jump Game reachability check embedded in the level loop.
"What if each index had a cost to jump from, and you minimised total cost?"
BFS no longer applies — unequal weights break the level structure. It becomes Dijkstra, or a DP
dp[j] = min over i in [j - nums[i], j-1] of (dp[i] + cost[i]), which a monotonic deque reduces toO(n)(09).The clean statement: BFS is Dijkstra specialised to uniform weights. Change the weights and you change algorithms.
"What if you could jump backwards too?"
Contiguity dies, so the two-integer frontier dies with it. Real BFS with a visited set,
O(n + E). If jump lengths are large this isO(n²)unless you maintain unvisited indices in aTreeSetand remove them as they're reached, which amortises toO(n log n).
"Minimum jumps to reach every index, not just the last?"
The same level scan already computes it — index
ibelongs to levelkwherekis its distance. Emitjumpsfor eachias the scan passes it. StillO(n), nowO(n)output.
"What if nums[i] could be up to 10^9 and n up to 10^5?"
Nothing changes for the greedy:
farthestsaturates atn - 1in effect andi + nums[i]fits inintat those sizes (10^5 + 10^9 < 2^31). TheO(n²)approaches become unusable, sinceE = sum(nums[i])would be10^14.That's the cleanest illustration of why collapsing the edges matters — the greedy never enumerates them.
"Is there a two-dimensional version?"
Longest Increasing Path in a Matrix is the closest relative in this set, though it's a longest path rather than a shortest one and needs memoised DFS. For shortest moves on a grid, plain BFS is the answer — the window collapse has no 2-D analogue, because a reachable region in two dimensions isn't described by one number.