Problem
You're standing at the first position of an array where each value tells you the maximum number of steps you can jump forward from that spot. Return the minimum number of jumps needed to reach the last position. The input always guarantees the last position is reachable.
- Input:
nums = [2, 3, 1, 1, 4] - Output:
2 - Explanation: Jump from index 0 to index 1 (within range 2), then from index 1 to index 4 (within range 3).
Counter-example: nums = [2, 3, 0, 1, 4] also returns 2 โ landing on the zero at index 2 is fine as long as you jumped over it or reached it already with enough jumps.
Intuition
At any point, every position we can currently stand on defines a "reach horizon" โ the farthest index reachable with one more jump. The greedy insight is that we never need to track which specific position we jump from: the moment we exhaust all positions reachable in k jumps, we increment the jump counter and our new horizon becomes the farthest we could have landed with k+1 jumps.
Approach 1 โ Dynamic Programming
For each index, look back at all previous positions that can reach it and take the minimum jump count plus one. This is correct but checks every prior position for every current one.
- Create a
min_jumpsarray initialized to infinity, withmin_jumps[0] = 0. - For each index
ifrom 1 ton-1, scan every earlier indexj. - If
j + nums[j] >= i, thenjcan reachiโ updatemin_jumps[i]ifmin_jumps[j] + 1is smaller. - Return
min_jumps[n-1].
1def jump(nums: list[int]) -> int:
2 n = len(nums)
3 min_jumps = [float('inf')] * n
4 min_jumps[0] = 0
5
6 for i in range(1, n):
7 for j in range(i):
8 if j + nums[j] >= i: # j can reach i
9 min_jumps[i] = min(min_jumps[i], min_jumps[j] + 1)
10
11 return min_jumps[-1]Time: O(nยฒ) โ for each of the n positions, we scan all previous positions.
Space: O(n) โ the min_jumps array.
Approach 2 โ Greedy (Single Pass)
Track current_end (farthest position reachable with the current number of jumps) and farthest (farthest position reachable by jumping once from anywhere in the current range). Each time we reach current_end, we commit to one more jump and extend our boundary.
- Initialize
jumps = 0,current_end = 0,farthest = 0. - Loop
ifrom0ton-2(stop before the last index โ arriving there doesn't cost a jump). - Update
farthest = max(farthest, i + nums[i]). - When
i == current_end, we've scanned the full current range โ incrementjumpsand advancecurrent_end = farthest. - Return
jumps.
1def jump(nums: list[int]) -> int:
2 jumps = 0
3 current_end = 0
4 farthest = 0
5
6 for i in range(len(nums) - 1): # stop before last; reaching it doesn't need a jump
7 farthest = max(farthest, i + nums[i])
8 if i == current_end: # exhausted every position reachable with current jumps
9 jumps += 1
10 current_end = farthest
11
12 return jumpsTime: O(n) โ a single pass through the array.
Space: O(1) โ only three integer variables.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Dynamic Programming | O(nยฒ) | O(n) | Easier to reason about; fine for small inputs |
| Greedy | O(n) | O(1) | Always prefer this โ constant space, linear time |
Common Mistakes
- Iterating to
n-1instead ofn-2โ the last index is the destination; whenireaches it, we've already jumped there and shouldn't increment the counter again. - Confusing this with Jump Game I โ the predecessor only asks "can you reach the end?" and never needs a jump counter; copy-pasting that solution here and adding a counter produces wrong results.
- Updating
current_endinside the farthest update โcurrent_endmust only change at the boundary check (if i == current_end), not every iteration; mixing the two causes the jump count to grow too fast. - Thinking you need to track the specific jump position โ you never need to record which index you jumped from; the greedy correctness relies only on knowing the maximum reach, not the source.
- Off-by-one on single-element input โ when
len(nums) == 1, the loop body never runs and the function correctly returns0.
Related Problems
Jump Gameโ the direct predecessor: same array, but only asks whether the end is reachable, not how many jumps it takesGas Stationโ greedy feasibility along a circular path using the same "track running totals and commit at a boundary" patternNon-Overlapping Intervalsโ greedy interval scheduling where you always commit to the locally optimal choicePartition Labelsโ tracking a "farthest reach" boundary to decide when to close each partitionMinimum Size Subarray Sumโ expand and contract a window greedily, a different flavor of "extend until a condition is met"