Facebook Pixel

1654. Minimum Jumps to Reach Home

Problem Description

A bug starts at position 0 on the x-axis and needs to reach its home at position x. The bug can move according to specific jumping rules:

  • It can jump exactly a positions forward (to the right)
  • It can jump exactly b positions backward (to the left)
  • It cannot jump backward twice consecutively
  • It cannot land on any forbidden positions

The bug is allowed to jump beyond its home position x, but it cannot jump to negative positions on the x-axis.

You're given:

  • An array forbidden containing positions where the bug cannot land
  • Integer a representing the forward jump distance
  • Integer b representing the backward jump distance
  • Integer x representing the bug's home position

The task is to find the minimum number of jumps needed for the bug to reach position x. If it's impossible to reach the home position, return -1.

For example, if a = 5, b = 1, x = 16, and forbidden = [14], the bug could reach position 16 by jumping forward multiple times (0 → 5 → 10 → 15 → 16 with one backward jump), avoiding the forbidden position 14.

The challenge involves finding the optimal path while respecting the movement constraints, particularly the restriction on consecutive backward jumps and avoiding forbidden positions.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: This problem can be modeled as a graph where each position on the x-axis is a node, and edges represent valid jumps between positions. The bug can jump from one position to another following specific rules.

Is it a tree?

  • No: The graph is not a tree because there can be multiple paths to reach the same position (you can reach a position by jumping forward multiple times or by combining forward and backward jumps).

Is the problem related to directed acyclic graphs?

  • No: The graph can have cycles since the bug can jump forward and then backward (as long as it doesn't jump backward twice in a row), potentially returning to previously visited positions.

Is the problem related to shortest paths?

  • Yes: We need to find the minimum number of jumps to reach position x, which is essentially finding the shortest path from position 0 to position x.

Is the graph Weighted?

  • No: Each jump counts as exactly 1 step regardless of the distance jumped (whether it's a positions forward or b positions backward). All edges have the same weight of 1.

BFS

  • Yes: Since we have an unweighted graph and need to find the shortest path, BFS is the optimal choice.

Conclusion: The flowchart suggests using BFS (Breadth-First Search) for finding the minimum number of jumps. BFS is perfect here because:

  1. We're working with an unweighted graph (each jump has equal cost)
  2. We need the shortest path from source (position 0) to target (position x)
  3. BFS naturally explores positions level by level, guaranteeing the first time we reach the target is via the shortest path
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

After identifying this as a shortest path problem in an unweighted graph, we need to think about how to model the state space properly. The key insight is that the bug's position alone isn't enough to describe its state - we also need to track whether it can jump backward or not.

Why? Because the constraint "cannot jump backward twice in a row" means that after a backward jump, the bug's options are limited (it can only jump forward). This creates two distinct states for each position:

  • State 1: Bug is at position i and can jump both forward and backward
  • State 2: Bug is at position i and can only jump forward (just jumped backward)

This leads us to represent each state as a tuple (position, can_jump_backward) where the second element is a flag (1 if can jump backward, 0 if cannot).

The next challenge is determining the search boundary. The bug can jump beyond position x, but how far should we allow it to explore?

If a >= b, once the bug goes beyond x + b, it can never return to x. Why? Because from any position greater than x + b, even a backward jump of size b keeps the bug beyond x, and it can't jump backward consecutively to get closer.

If a < b, the analysis is more complex, but through mathematical analysis, we can show that exploring up to position 6000 is sufficient for all valid test cases.

Using BFS with this state representation ensures we find the minimum jumps because:

  1. BFS explores states level by level (each level represents one more jump)
  2. The first time we reach position x is guaranteed to be via the minimum number of jumps
  3. By tracking visited states as (position, direction) pairs, we avoid revisiting the same state and ensure termination

The forbidden positions are simply treated as walls - we never add them to our queue during the search. This naturally prevents the bug from landing on those positions.

Learn more about Breadth-First Search and Dynamic Programming patterns.

Solution Approach

Following the BFS strategy identified earlier, let's implement the solution step by step:

1. Initialize Data Structures:

  • Create a set s from the forbidden array for O(1) lookup of forbidden positions
  • Use a deque q for BFS, starting with initial state (0, 1) where 0 is the starting position and 1 means the bug can jump backward
  • Maintain a visited set vis to track states we've already explored as (position, direction) pairs
  • Initialize ans = 0 to count the number of jumps

2. BFS Implementation:

while q:
    for _ in range(len(q)):  # Process all states at current level
        i, k = q.popleft()    # Get current position and direction flag

We process all states at the current level before incrementing the jump counter, ensuring we track the minimum jumps correctly.

3. Check for Target:

if i == x:
    return ans

If we've reached the target position x, return the current number of jumps immediately.

4. Generate Next States:

nxt = [(i + a, 1)]        # Always can jump forward
if k & 1:                  # If k=1, can also jump backward
    nxt.append((i - b, 0)) # After backward jump, set flag to 0
  • Forward jump: New position is i + a, and the bug can jump backward from there (flag = 1)
  • Backward jump: Only possible if k = 1. New position is i - b, and the bug cannot jump backward again (flag = 0)

5. Validate and Add New States:

for j, k in nxt:
    if 0 <= j < 6000 and j not in s and (j, k) not in vis:
        q.append((j, k))
        vis.add((j, k))

For each potential next state, we check:

  • Position is non-negative: j >= 0
  • Position is within boundary: j < 6000 (as explained in the reference approach)
  • Position is not forbidden: j not in s
  • State hasn't been visited: (j, k) not in vis

6. Increment Jump Counter: After processing all states at the current level, increment ans += 1 before moving to the next level.

7. Return -1 if Unreachable: If the queue becomes empty without finding the target, return -1.

The time complexity is O(max_position) since each position can be visited at most twice (once with each direction flag). The space complexity is also O(max_position) for the visited set and queue.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's trace through a small example to illustrate the solution approach:

Given: a = 3, b = 1, x = 6, forbidden = [4]

Initial Setup:

  • Start at position 0, can jump backward (state: (0, 1))
  • Queue: [(0, 1)]
  • Visited: {(0, 1)}
  • Jumps: 0

Level 0 (0 jumps):

  • Process (0, 1):
    • Position is 0, not equal to target 6
    • Can jump forward to 3: (0 + 3, 1) = (3, 1)
    • Can jump backward (since flag=1) to -1: (0 - 1, 0) = (-1, 0) ❌ (negative position, invalid)
    • Add (3, 1) to queue
  • Queue: [(3, 1)]
  • Visited: {(0, 1), (3, 1)}
  • Increment jumps to 1

Level 1 (1 jump):

  • Process (3, 1):
    • Position is 3, not equal to target 6
    • Can jump forward to 6: (3 + 3, 1) = (6, 1)
    • Can jump backward to 2: (3 - 1, 0) = (2, 0)
    • Add both to queue
  • Queue: [(6, 1), (2, 0)]
  • Visited: {(0, 1), (3, 1), (6, 1), (2, 0)}
  • Increment jumps to 2

Level 2 (2 jumps):

  • Process (6, 1):
    • Position is 6, equals target! Return 2

The bug reaches home in 2 jumps: 0 → 3 → 6

Notice how:

  • Position 4 is forbidden, so we never add it to the queue
  • Each state tracks both position and whether backward jump is allowed
  • The backward jump from position 3 creates state (2, 0) where the bug can't jump backward again
  • BFS guarantees we find the shortest path first

Solution Implementation

1class Solution:
2    def minimumJumps(self, forbidden: List[int], a: int, b: int, x: int) -> int:
3        # Create a set of forbidden positions for O(1) lookup
4        forbidden_positions = set(forbidden)
5      
6        # Initialize BFS queue with starting position (position=0, can_jump_back=1)
7        # The second element tracks if we can jump backward (1=yes, 0=no)
8        queue = deque([(0, 1)])
9      
10        # Track visited states as (position, can_jump_back) pairs
11        visited = {(0, 1)}
12      
13        # Track the number of jumps taken
14        jumps = 0
15      
16        # BFS to find minimum jumps to reach target position x
17        while queue:
18            # Process all positions at current jump level
19            level_size = len(queue)
20            for _ in range(level_size):
21                current_pos, can_jump_back = queue.popleft()
22              
23                # Check if we've reached the target
24                if current_pos == x:
25                    return jumps
26              
27                # Generate next possible positions
28                next_positions = [(current_pos + a, 1)]  # Forward jump always allowed
29              
30                # Backward jump only allowed if we didn't just jump backward
31                if can_jump_back & 1:  # Equivalent to: if can_jump_back == 1
32                    next_positions.append((current_pos - b, 0))
33              
34                # Process each next position
35                for next_pos, next_can_jump_back in next_positions:
36                    # Check boundaries, forbidden positions, and if already visited
37                    if (0 <= next_pos < 6000 and 
38                        next_pos not in forbidden_positions and 
39                        (next_pos, next_can_jump_back) not in visited):
40                      
41                        queue.append((next_pos, next_can_jump_back))
42                        visited.add((next_pos, next_can_jump_back))
43          
44            # Increment jump counter after processing current level
45            jumps += 1
46      
47        # Target position unreachable
48        return -1
49
1class Solution {
2    public int minimumJumps(int[] forbidden, int a, int b, int x) {
3        // Create a set to store forbidden positions for O(1) lookup
4        Set<Integer> forbiddenSet = new HashSet<>();
5        for (int position : forbidden) {
6            forbiddenSet.add(position);
7        }
8      
9        // Initialize BFS queue with starting position
10        // Each element is [position, direction] where direction: 1 = can go backward, 0 = cannot go backward
11        Deque<int[]> queue = new ArrayDeque<>();
12        queue.offer(new int[] {0, 1});
13      
14        // Set upper bound for search space
15        final int MAX_POSITION = 6000;
16      
17        // Track visited states: [position][direction]
18        // visited[i][0] = visited position i after backward jump
19        // visited[i][1] = visited position i after forward jump
20        boolean[][] visited = new boolean[MAX_POSITION][2];
21        visited[0][1] = true;
22      
23        // BFS to find minimum jumps
24        for (int jumps = 0; !queue.isEmpty(); jumps++) {
25            int levelSize = queue.size();
26          
27            // Process all nodes at current level
28            for (int i = 0; i < levelSize; i++) {
29                int[] current = queue.poll();
30                int currentPosition = current[0];
31                int canGoBackward = current[1];
32              
33                // Check if we reached the target
34                if (currentPosition == x) {
35                    return jumps;
36                }
37              
38                // Generate next possible positions
39                List<int[]> nextPositions = new ArrayList<>();
40              
41                // Always can jump forward by distance a
42                nextPositions.add(new int[] {currentPosition + a, 1});
43              
44                // Can only jump backward if previous move was forward (canGoBackward = 1)
45                if (canGoBackward == 1) {
46                    nextPositions.add(new int[] {currentPosition - b, 0});
47                }
48              
49                // Process each next position
50                for (int[] next : nextPositions) {
51                    int nextPosition = next[0];
52                    int nextDirection = next[1];
53                  
54                    // Check if next position is valid and unvisited
55                    if (nextPosition >= 0 && 
56                        nextPosition < MAX_POSITION && 
57                        !forbiddenSet.contains(nextPosition) && 
58                        !visited[nextPosition][nextDirection]) {
59                      
60                        queue.offer(new int[] {nextPosition, nextDirection});
61                        visited[nextPosition][nextDirection] = true;
62                    }
63                }
64            }
65        }
66      
67        // Target position unreachable
68        return -1;
69    }
70}
71
1class Solution {
2public:
3    int minimumJumps(vector<int>& forbidden, int a, int b, int x) {
4        // Create a set of forbidden positions for O(1) lookup
5        unordered_set<int> forbiddenSet(forbidden.begin(), forbidden.end());
6      
7        // BFS queue: pair<position, direction>
8        // direction: 1 = can jump backward, 0 = cannot jump backward
9        queue<pair<int, int>> bfsQueue;
10        bfsQueue.emplace(0, 1);  // Start at position 0, can jump backward
11      
12        // Maximum position to consider (problem constraint)
13        const int MAX_POSITION = 6000;
14      
15        // Visited array: visited[position][direction]
16        // visited[i][0] = visited position i after jumping forward
17        // visited[i][1] = visited position i after jumping backward
18        bool visited[MAX_POSITION][2];
19        memset(visited, false, sizeof(visited));
20        visited[0][1] = true;  // Mark starting position as visited
21      
22        // BFS to find minimum jumps
23        for (int jumps = 0; !bfsQueue.empty(); ++jumps) {
24            // Process all nodes at current level
25            int levelSize = bfsQueue.size();
26            for (int i = 0; i < levelSize; ++i) {
27                // Get current position and direction state
28                auto [currentPos, canJumpBack] = bfsQueue.front();
29                bfsQueue.pop();
30              
31                // Check if we reached the target
32                if (currentPos == x) {
33                    return jumps;
34                }
35              
36                // Generate next possible positions
37                vector<pair<int, int>> nextStates = {{currentPos + a, 1}};  // Jump forward
38              
39                // Can only jump backward if previous move wasn't backward
40                if (canJumpBack & 1) {
41                    nextStates.emplace_back(currentPos - b, 0);  // Jump backward
42                }
43              
44                // Process each next state
45                for (auto [nextPos, nextDirection] : nextStates) {
46                    // Check if next position is valid:
47                    // 1. Within bounds [0, MAX_POSITION)
48                    // 2. Not in forbidden set
49                    // 3. Not visited with this direction
50                    if (nextPos >= 0 && nextPos < MAX_POSITION && 
51                        !forbiddenSet.count(nextPos) && 
52                        !visited[nextPos][nextDirection]) {
53                      
54                        visited[nextPos][nextDirection] = true;
55                        bfsQueue.emplace(nextPos, nextDirection);
56                    }
57                }
58            }
59        }
60      
61        // Target position unreachable
62        return -1;
63    }
64};
65
1/**
2 * Finds the minimum number of jumps needed to reach position x from position 0
3 * @param forbidden - Array of positions that cannot be visited
4 * @param a - Forward jump distance
5 * @param b - Backward jump distance
6 * @param x - Target position to reach
7 * @returns Minimum number of jumps to reach x, or -1 if impossible
8 */
9function minimumJumps(forbidden: number[], a: number, b: number, x: number): number {
10    // Set of forbidden positions for O(1) lookup
11    const forbiddenSet: Set<number> = new Set(forbidden);
12  
13    // BFS queue storing [position, canJumpBackward] pairs
14    // canJumpBackward: 1 means can jump backward, 0 means cannot (just jumped backward)
15    const queue: [number, number][] = [[0, 1]];
16  
17    // Maximum position boundary to prevent infinite exploration
18    const maxPosition = 6000;
19  
20    // Visited states: visited[position][canJumpBackward]
21    // visited[i][0] - visited position i when cannot jump backward
22    // visited[i][1] - visited position i when can jump backward
23    const visited: boolean[][] = Array.from({ length: maxPosition }, () => [false, false]);
24    visited[0][1] = true;
25  
26    // BFS level by level
27    for (let steps = 0; queue.length > 0; steps++) {
28        // Process all nodes at current level
29        const currentLevelSize = queue.length;
30      
31        for (let i = 0; i < currentLevelSize; i++) {
32            const [currentPosition, canJumpBackward] = queue.shift()!;
33          
34            // Check if we reached the target
35            if (currentPosition === x) {
36                return steps;
37            }
38          
39            // Generate next possible positions
40            const nextPositions: [number, number][] = [];
41          
42            // Always can jump forward
43            nextPositions.push([currentPosition + a, 1]);
44          
45            // Can only jump backward if allowed (didn't just jump backward)
46            if (canJumpBackward === 1) {
47                nextPositions.push([currentPosition - b, 0]);
48            }
49          
50            // Process each next position
51            for (const [nextPosition, nextCanJumpBackward] of nextPositions) {
52                // Check if position is valid and not visited in this state
53                if (nextPosition >= 0 && 
54                    nextPosition < maxPosition && 
55                    !forbiddenSet.has(nextPosition) && 
56                    !visited[nextPosition][nextCanJumpBackward]) {
57                  
58                    visited[nextPosition][nextCanJumpBackward] = true;
59                    queue.push([nextPosition, nextCanJumpBackward]);
60                }
61            }
62        }
63    }
64  
65    // Target position cannot be reached
66    return -1;
67}
68

Time and Space Complexity

The time complexity is O(M), where M is the upper bound for positions we need to explore (in this case, M = 6000). This is because:

  • We use BFS to explore positions from 0 to at most 6000
  • Each position can be visited at most twice (once with the ability to jump backward k=1, and once without k=0)
  • Therefore, we process at most 2M states, which is O(M)

The space complexity is O(M) as well, where M ≤ 6000. This is because:

  • The vis set stores at most 2M states (each position from 0 to M with two possible states for backward jump availability)
  • The queue q in the worst case could contain O(M) elements
  • The forbidden set s takes O(|forbidden|) space, which is bounded by O(M)
  • Overall space usage is dominated by O(M)

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

Common Pitfalls

1. Incorrect State Representation - Treating Position Alone as State

One of the most common mistakes is using only the position as the state in BFS, ignoring whether the bug can jump backward from that position.

Incorrect approach:

visited = {0}  # Only tracking positions
queue = deque([0])

while queue:
    pos = queue.popleft()
    # This doesn't track if we can jump backward!

Why this fails: Consider reaching position 20 via two different paths:

  • Path 1: Forward jumps only → Can jump backward from position 20
  • Path 2: Ends with a backward jump → Cannot jump backward from position 20

These are different states! The bug might need to jump backward from position 20 to reach the target, which is only possible via Path 1.

Solution: Always track states as (position, can_jump_backward) tuples:

visited = {(0, 1)}  # Track both position and direction capability
queue = deque([(0, 1)])

2. Setting an Insufficient Upper Boundary

Another critical pitfall is setting the upper boundary too low or not setting one at all.

Incorrect approach:

if 0 <= next_pos <= x and next_pos not in forbidden_positions:
    # This limits exploration to positions <= x

Why this fails: The optimal path might require jumping beyond the target position x and then jumping backward. For example:

  • If a = 10, b = 3, x = 7
  • Optimal path: 0 → 10 → 7 (2 jumps)
  • Without going beyond x, you'd need more jumps

Solution: Use a sufficiently large upper bound based on problem constraints:

if 0 <= next_pos < 6000 and next_pos not in forbidden_positions:
    # 6000 is proven sufficient for the given constraints

The bound of 6000 works because:

  • Maximum value in constraints is 2000 (for forbidden positions and x)
  • After analysis, positions beyond max(x, max(forbidden)) + 2 * max(a, b) rarely lead to optimal solutions
  • 6000 provides a safe margin for all valid test cases

3. Mishandling the Direction Flag Logic

Incorrect approach:

# Wrong: Using the same flag value after both forward and backward jumps
next_positions = [(current_pos + a, 0), (current_pos - b, 0)]

Why this fails: After a forward jump, the bug should be able to jump backward (flag should be 1), but after a backward jump, it shouldn't (flag should be 0).

Solution: Set the flag correctly based on jump direction:

next_positions = [(current_pos + a, 1)]  # Can jump backward after forward jump
if can_jump_back:
    next_positions.append((current_pos - b, 0))  # Cannot jump backward after backward jump
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following problems can be solved with backtracking (select multiple)


Recommended Readings

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

Load More