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 positionx
.
Is the graph Weighted?
- No: Each jump counts as exactly 1 step regardless of the distance jumped (whether it's
a
positions forward orb
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:
- We're working with an unweighted graph (each jump has equal cost)
- We need the shortest path from source (position 0) to target (position x)
- BFS naturally explores positions level by level, guaranteeing the first time we reach the target is via the shortest path
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:
- BFS explores states level by level (each level represents one more jump)
- The first time we reach position
x
is guaranteed to be via the minimum number of jumps - 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 theforbidden
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 isi - 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 EvaluatorExample 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 most6000
- Each position can be visited at most twice (once with the ability to jump backward
k=1
, and once withoutk=0
) - Therefore, we process at most
2M
states, which isO(M)
The space complexity is O(M)
as well, where M ≤ 6000
. This is because:
- The
vis
set stores at most2M
states (each position from0
toM
with two possible states for backward jump availability) - The queue
q
in the worst case could containO(M)
elements - The forbidden set
s
takesO(|forbidden|)
space, which is bounded byO(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
Which of the following problems can be solved with backtracking (select multiple)
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!