Facebook Pixel

45. Jump Game II

Problem Description

You have an array nums where each element tells you the maximum distance you can jump forward from that position. Starting at index 0, you need to find the minimum number of jumps required to reach the last index.

Rules:

  • From position i, you can jump to any position between i+1 and i + nums[i] (as long as it's within the array bounds)
  • You start at index 0
  • You want to reach index n-1 (the last position)
  • It's guaranteed that you can always reach the last index

Example: If nums = [2, 3, 1, 1, 4]:

  • From index 0 (value 2): you can jump to index 1 or 2
  • From index 1 (value 3): you can jump to index 2, 3, or 4
  • The minimum path would be: 0 → 1 → 4 (2 jumps)

The greedy approach works by tracking the farthest position reachable (mx) as you scan through the array. When you reach the boundary of your current jump range (last), you must make another jump, updating the boundary to the new farthest position you've discovered. This ensures you always make the optimal jump that covers the maximum distance possible.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think of this problem like planning a road trip where you need to make gas stops. At each gas station, you know the maximum distance you can travel before needing to refuel. You want to minimize the number of stops to reach your destination.

The key insight is that we don't need to try every possible path - we can be greedy. When we must make a jump (when we reach the end of our current jump's range), we should always jump to the position that allows us to reach the farthest in the next jump.

Imagine standing at the starting position. You know you can reach several positions with your first jump. Instead of immediately deciding where to land, you can explore all positions within your first jump's reach and check how far each of them could take you in the subsequent jump. The position that gives you the maximum reach for the next jump is your optimal choice.

Here's the clever part: we don't need to explicitly track where we jump to. We just need to know:

  1. The boundary of our current jump (last) - when we reach this, we must jump again
  2. The farthest position we can reach from any position within our current jump range (mx)

As we scan through positions from left to right:

  • We continuously update mx with the farthest reachable position
  • When we hit the boundary of our current jump (i == last), we know we must jump
  • Our next jump will land somewhere that can reach mx, so we set last = mx
  • We increment our jump counter

This way, we're always making the optimal jump that maximizes our reach, ensuring the minimum number of jumps to reach the end.

Learn more about Greedy and Dynamic Programming patterns.

Solution Approach

The implementation uses a greedy algorithm with three key variables:

  • ans: counts the number of jumps made
  • mx: tracks the farthest position reachable from positions we've visited
  • last: marks the boundary of the current jump (when we reach this position, we must jump again)

Algorithm Steps:

  1. Initialize variables: Start with ans = mx = last = 0. We begin at position 0 with 0 jumps made.

  2. Iterate through the array: Loop through positions [0, n-2] (we exclude the last position since we don't need to jump from there).

  3. Update maximum reach: For each position i, calculate the farthest position reachable from here: mx = max(mx, i + nums[i]). This keeps track of the best possible reach we've discovered so far.

  4. Check if we need to jump: When i == last, we've reached the boundary of our current jump range. This means:

    • We must make another jump (ans += 1)
    • Update the boundary to the farthest position we can reach (last = mx)
  5. Return the result: After processing all positions, ans contains the minimum number of jumps.

Why this works:

The algorithm ensures optimality because:

  • We only jump when necessary (when reaching the current jump's boundary)
  • When we jump, we implicitly choose the best position that was available in our range (the one that led to mx)
  • By updating last = mx, we're essentially saying "our next jump will land somewhere that can reach at least position mx"

Example walkthrough with nums = [2,3,1,1,4]:

  • i=0: mx = max(0, 0+2) = 2, i == last so jump (ans=1, last=2)
  • i=1: mx = max(2, 1+3) = 4
  • i=2: mx = max(4, 2+1) = 4, i == last so jump (ans=2, last=4)
  • i=3: mx = max(4, 3+1) = 4
  • Return ans = 2

Time Complexity: O(n) - single pass through the array
Space Complexity: O(1) - only using a constant amount of extra space

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through the algorithm with nums = [2, 3, 0, 1, 4]:

Initial State:

  • ans = 0 (no jumps yet)
  • mx = 0 (farthest we can reach)
  • last = 0 (current jump boundary)

Step 1: i = 0

  • From index 0, we can jump up to 2 positions (reaching index 2)
  • Update: mx = max(0, 0 + 2) = 2
  • Since i == last (0 == 0), we've hit our boundary and must jump:
    • Increment jumps: ans = 1
    • Set new boundary: last = 2
  • Status: Made 1 jump, can reach up to index 2

Step 2: i = 1

  • From index 1, we can jump up to 3 positions (reaching index 4)
  • Update: mx = max(2, 1 + 3) = 4
  • Since i != last (1 != 2), continue exploring
  • Status: Still in first jump range, discovered we can reach index 4

Step 3: i = 2

  • From index 2, we can jump 0 positions (stuck at index 2)
  • Update: mx = max(4, 2 + 0) = 4
  • Since i == last (2 == 2), we've hit our boundary and must jump:
    • Increment jumps: ans = 2
    • Set new boundary: last = 4
  • Status: Made 2 jumps, can reach up to index 4

Step 4: i = 3

  • From index 3, we can jump 1 position (reaching index 4)
  • Update: mx = max(4, 3 + 1) = 4
  • Since i != last (3 != 4), continue
  • Loop ends (we don't process index 4)

Result: ans = 2 jumps

The path taken was effectively: 0 → 1 or 2 → 4. The algorithm doesn't explicitly track which position we jump to, but by setting last = mx when we jump, we ensure we're always making the optimal choice that maximizes our reach.

Solution Implementation

1class Solution:
2    def jump(self, nums: List[int]) -> int:
3        # Initialize variables
4        jump_count = 0  # Number of jumps made
5        current_jump_end = 0  # The farthest index reachable with current number of jumps
6        farthest_reachable = 0  # The farthest index we can reach overall
7      
8        # Iterate through all elements except the last one
9        # We don't need to check the last element since we want to reach it, not jump from it
10        for i in range(len(nums) - 1):
11            # Update the farthest index we can reach from current position
12            farthest_reachable = max(farthest_reachable, i + nums[i])
13          
14            # If we've reached the end of the current jump's range
15            if i == current_jump_end:
16                # We must make another jump
17                jump_count += 1
18                # Update the range of the next jump to the farthest we can reach
19                current_jump_end = farthest_reachable
20      
21        return jump_count
22
1class Solution {
2    public int jump(int[] nums) {
3        // Track the minimum number of jumps needed
4        int jumps = 0;
5      
6        // Track the farthest position reachable with current and previous jumps
7        int maxReachable = 0;
8      
9        // Track the end of the current jump's range
10        int currentJumpEnd = 0;
11      
12        // Iterate through array except the last element (we don't need to jump from the last position)
13        for (int i = 0; i < nums.length - 1; i++) {
14            // Update the farthest position we can reach from current position
15            maxReachable = Math.max(maxReachable, i + nums[i]);
16          
17            // If we've reached the end of the current jump's range
18            if (i == currentJumpEnd) {
19                // We must make another jump
20                jumps++;
21              
22                // Update the range of the next jump to the farthest position we can reach
23                currentJumpEnd = maxReachable;
24            }
25        }
26      
27        return jumps;
28    }
29}
30
1class Solution {
2public:
3    int jump(vector<int>& nums) {
4        // Track the minimum number of jumps needed
5        int jumpCount = 0;
6      
7        // Track the farthest position reachable so far
8        int maxReach = 0;
9      
10        // Track the end of the current jump range
11        int currentJumpEnd = 0;
12      
13        // Iterate through array except the last element (we don't need to jump from the last position)
14        for (int i = 0; i < nums.size() - 1; ++i) {
15            // Update the farthest position we can reach from current position
16            maxReach = max(maxReach, i + nums[i]);
17          
18            // If we've reached the end of the current jump range
19            if (i == currentJumpEnd) {
20                // We must make a jump to continue
21                ++jumpCount;
22              
23                // Update the range end to the farthest position we can reach
24                currentJumpEnd = maxReach;
25            }
26        }
27      
28        return jumpCount;
29    }
30};
31
1/**
2 * Finds the minimum number of jumps needed to reach the last index
3 * @param nums - Array where each element represents the maximum jump length from that position
4 * @returns The minimum number of jumps to reach the last index
5 */
6function jump(nums: number[]): number {
7    // Initialize variables
8    let jumpCount: number = 0;           // Total number of jumps made
9    let currentMaxReach: number = 0;      // Maximum index reachable so far
10    let currentJumpEnd: number = 0;       // End boundary of the current jump
11  
12    // Iterate through array except the last element (we don't need to jump from the last position)
13    for (let i = 0; i < nums.length - 1; i++) {
14        // Update the farthest index we can reach from current position
15        currentMaxReach = Math.max(currentMaxReach, i + nums[i]);
16      
17        // When we reach the end of the current jump range
18        if (currentJumpEnd === i) {
19            // Make a new jump
20            jumpCount++;
21            // Update the end boundary to the farthest position we can reach
22            currentJumpEnd = currentMaxReach;
23        }
24    }
25  
26    return jumpCount;
27}
28

Time and Space Complexity

Time Complexity: O(n) where n is the length of the input array nums. The algorithm iterates through the array exactly once (excluding the last element due to nums[:-1]), performing constant time operations for each element - specifically a max comparison, an equality check, and potentially incrementing counters.

Space Complexity: O(1) - The algorithm uses only a fixed amount of extra space regardless of input size. It maintains three variables (ans, mx, and last) plus the loop variable i, all of which require constant space. The slicing operation nums[:-1] in Python creates an iterator rather than a new list, so no additional space proportional to the input is used.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Including the Last Index in the Loop

Pitfall: Iterating through all elements including the last one (range(len(nums)) instead of range(len(nums) - 1)).

Why it's wrong: If you include the last index in your loop and it happens to be at the boundary of a jump (i == current_jump_end), you'll incorrectly increment the jump count even though you've already reached the destination.

Example causing error:

  • nums = [2, 1]: If you loop through both indices, when i=1 and current_jump_end=1, you'd incorrectly add an extra jump.

Solution: Always loop only up to len(nums) - 1:

for i in range(len(nums) - 1):  # Correct
    # Process...

2. Updating Jump Boundary Before Checking

Pitfall: Updating farthest_reachable after checking if i == current_jump_end.

Why it's wrong: You need to know the farthest reachable position from all indices within the current jump range before deciding where the next jump should end.

Incorrect order:

if i == current_jump_end:
    jump_count += 1
    current_jump_end = farthest_reachable
# Wrong: updating after the check
farthest_reachable = max(farthest_reachable, i + nums[i])

Solution: Always update farthest_reachable before the boundary check:

# Correct order
farthest_reachable = max(farthest_reachable, i + nums[i])
if i == current_jump_end:
    jump_count += 1
    current_jump_end = farthest_reachable

3. Edge Case: Single Element Array

Pitfall: Not handling arrays with only one element properly.

Why it matters: With nums = [0], you're already at the destination and need 0 jumps. The algorithm handles this correctly by not entering the loop at all (since range(0) is empty).

Solution: The current implementation handles this correctly, but be aware that:

  • Arrays with length 1 should return 0
  • The loop range len(nums) - 1 naturally handles this case

4. Misunderstanding the Jump Semantics

Pitfall: Thinking you must jump exactly nums[i] positions instead of up to nums[i] positions.

Why it's wrong: The problem allows jumping anywhere from 1 to nums[i] positions forward, not exactly nums[i] positions.

Solution: Remember that farthest_reachable tracks the maximum possible reach, and the greedy choice implicitly selects the best intermediate position when updating the boundary.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What does the following code do?

1def f(arr1, arr2):
2  i, j = 0, 0
3  new_arr = []
4  while i < len(arr1) and j < len(arr2):
5      if arr1[i] < arr2[j]:
6          new_arr.append(arr1[i])
7          i += 1
8      else:
9          new_arr.append(arr2[j])
10          j += 1
11  new_arr.extend(arr1[i:])
12  new_arr.extend(arr2[j:])
13  return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2  int i = 0, j = 0;
3  List<Integer> newArr = new ArrayList<>();
4
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.add(arr1[i]);
8          i++;
9      } else {
10          newArr.add(arr2[j]);
11          j++;
12      }
13  }
14
15  while (i < arr1.length) {
16      newArr.add(arr1[i]);
17      i++;
18  }
19
20  while (j < arr2.length) {
21      newArr.add(arr2[j]);
22      j++;
23  }
24
25  return newArr;
26}
27
1function f(arr1, arr2) {
2  let i = 0, j = 0;
3  let newArr = [];
4  
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.push(arr1[i]);
8          i++;
9      } else {
10          newArr.push(arr2[j]);
11          j++;
12      }
13  }
14  
15  while (i < arr1.length) {
16      newArr.push(arr1[i]);
17      i++;
18  }
19  
20  while (j < arr2.length) {
21      newArr.push(arr2[j]);
22      j++;
23  }
24  
25  return newArr;
26}
27

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More