Minimum Jumps to Reach Home
MedPrompt
A certain bug's home is on the x-axis at position x. Help them get there from position 0.
The bug jumps according to the following rules:
- It can jump exactly
apositions forward (to the right). - It can jump exactly
bpositions backward (to the left). - It cannot jump backward twice in a row.
- It cannot jump to any
forbiddenpositions.
The bug may jump forward beyond its home, but it cannot jump to positions numbered with negative integers.
Given an array of integers forbidden, where forbidden[i] means that the bug cannot jump to the position forbidden[i], and integers a, b, and x, return the minimum number of jumps needed for the bug to reach its home. If there is no possible sequence of jumps that lands the bug on position x, return -1.
Example 1:
Input: forbidden = [14,4,18,1,15], a = 3, b = 15, x = 9
Output: 3
Explanation: 3 jumps forward (0 -> 3 -> 6 -> 9) will get the bug home.Example 2:
Input: forbidden = [8,3,16,6,12,20], a = 15, b = 13, x = 11
Output: -1Example 3:
Input: forbidden = [1,6,2,14,5,17,4], a = 16, b = 9, x = 7
Output: 2
Explanation: One jump forward (0 -> 16) then one jump backward (16 -> 7) will get the bug home.
Constraints:
1 <= forbidden.length <= 10001 <= a, b, forbidden[i] <= 20000 <= x <= 2000- All the elements in
forbiddenare distinct. - Position
xis not forbidden.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses a classic brute-force recursive method to explore all possible sequences of jumps. It starts from position 0 and, at each step, recursively explores the two possibilities: a forward jump and (if allowed) a backward jump. This method is simple to conceptualize but is highly inefficient as it explores many redundant paths and doesn't inherently find the shortest path without exploring the entire search space.
Algorithm
- Define a recursive function, say
dfs(position, jumps, isLastJumpBackward, pathVisited). pathVisitedis a set of states(position, isLastJumpBackward)visited in the current recursive path to detect cycles.- The main function initializes a global minimum jumps variable,
minJumps, to infinity and callsdfs(0, 0, false, new HashSet<>()). - Inside
dfs:- If the current number of
jumpsis already greater than or equal tominJumps, prune the search by returning. - If
position == x, a path is found. UpdateminJumps = min(minJumps, jumps)and return. - Check for invalid states: if
positionis negative, forbidden, too far, or the state(position, isLastJumpBackward)is already inpathVisited, return to avoid cycles and invalid moves. - Add the current state to
pathVisitedto mark it as part of the current path. - Recursively call for the next possible moves:
- Forward jump:
dfs(position + a, jumps + 1, false, pathVisited). - Backward jump: If
isLastJumpBackwardis false,dfs(position - b, jumps + 1, true, pathVisited).
- Forward jump:
- Backtrack by removing the current state from
pathVisitedbefore returning.
- If the current number of
- After the initial call returns, if
minJumpsis still infinity, it meansxis unreachable. Otherwise,minJumpsholds the answer.
Walkthrough
The brute-force Depth First Search (DFS) approach tries to find a solution by exploring every possible path from the starting position. A recursive function is defined to navigate through the state space, where a state is defined by the bug's current position and whether the last jump was backward.
To prevent infinite loops (e.g., 0 -> a -> 0 -> a ...), we must keep track of the states visited along the current path. A major drawback is that this method will explore paths exhaustively. For instance, it might find a very long path to the solution first and will only find the shortest path after exploring many other (potentially shorter) paths. This leads to a very high time complexity, making it unsuitable for the given constraints.
import java.util.HashSet;import java.util.Set; class Solution { int minJumps = Integer.MAX_VALUE; Set<Integer> forbiddenSet; int a, b, x; int limit; public int minimumJumps(int[] forbidden, int a, int b, int x) { this.forbiddenSet = new HashSet<>(); int maxForbidden = 0; for (int f : forbidden) { this.forbiddenSet.add(f); maxForbidden = Math.max(maxForbidden, f); } this.a = a; this.b = b; this.x = x; // A loose upper bound to prune search space this.limit = Math.max(x, maxForbidden) + a + b; // pathVisited stores "pos,isBackward" strings dfs(0, 0, false, new HashSet<String>()); return minJumps == Integer.MAX_VALUE ? -1 : minJumps; } private void dfs(int pos, int jumps, boolean isLastBackward, Set<String> pathVisited) { if (pos == x) { minJumps = Math.min(minJumps, jumps); return; } // Pruning conditions if (pos < 0 || pos > limit || forbiddenSet.contains(pos) || jumps >= minJumps) { return; } String state = pos + "," + isLastBackward; if (pathVisited.contains(state)) { return; // Cycle detected in current path } pathVisited.add(state); // Forward jump dfs(pos + a, jumps + 1, false, pathVisited); // Backward jump if (!isLastBackward) { dfs(pos - b, jumps + 1, true, pathVisited); } pathVisited.remove(state); // Backtrack }}Complexity
Time
O(2^L), where L is the length of the path. The complexity is exponential as it potentially explores all possible paths up to a certain length, leading to a Time Limit Exceeded error on most platforms.
Space
O(L), where L is the maximum depth of the recursion. This space is used for the recursion stack and the `pathVisited` set.
Trade-offs
Pros
Conceptually simple and easy to implement for a basic understanding of the problem.
Cons
Extremely inefficient due to re-computation of paths for the same state.
Prone to stack overflow for deep search paths without aggressive pruning.
Does not guarantee finding the shortest path first; it must explore the entire search space to find the minimum.
Can easily get stuck in infinite loops if cycles are not handled correctly.
This problem can be modeled as finding the shortest path in an unweighted graph. Breadth-First Search (BFS) is the perfect algorithm for this task. We treat each possible state of the bug—its position and whether its last jump was backward—as a node in a graph. A jump represents an edge. BFS explores the graph layer by layer, guaranteeing that the first time it reaches the target position x, it does so via the minimum number of jumps.
Algorithm
- Initialize a
Setwith allforbiddenpositions for O(1) lookups. - Determine a safe upper bound for the search space. A value like
max(x, max(forbidden)) + a + bis sufficient to ensure we don't miss any potential solutions. - Initialize a queue for BFS and add the starting state
(position=0, lastJumpWasBackward=false). - Use a 2D boolean array
visited[position][wasBackward]to keep track of visited states to avoid redundant computations and cycles. - Start the BFS loop, proceeding level by level. Each level corresponds to one additional jump.
- In each level, dequeue all states added in the previous level.
- For each dequeued state
(pos, lastWasBackward):- If
pos == x, the shortest path is found. Return the current jump count. - Explore the forward jump: Calculate
nextPos = pos + a. IfnextPosis within the bounds, not forbidden, and the state(nextPos, false)has not been visited, add it to the queue and mark it as visited. - Explore the backward jump: If
lastWasBackwardis false, calculatenextPos = pos - b. IfnextPosis non-negative, not forbidden, and the state(nextPos, true)has not been visited, add it to the queue and mark it as visited.
- If
- If the queue becomes empty and
xhas not been reached, it's impossible to get home. Return -1.
Walkthrough
The key insight is to represent the problem as a shortest path problem on a state graph. A state is defined by a pair: (position, was_last_jump_backward). The was_last_jump_backward flag is crucial because it determines whether a backward jump is allowed from the current position.
We start a BFS from the initial state (0, false). A queue stores the states to be visited. To avoid infinite loops and redundant work, we use a visited data structure (e.g., a 2D boolean array) to keep track of states we've already processed. The BFS proceeds in levels, where each level corresponds to one jump. When we first encounter the target position x, we are guaranteed to have found a path with the minimum number of jumps because BFS explores all paths of length k before exploring any path of length k+1.
An important consideration is the search space boundary. The bug can jump beyond x. We need to define a reasonable upper limit for positions to explore. A safe and effective limit is max(x, max(forbidden)) + a + b, which is well within the limits imposed by the problem constraints.
import java.util.*; class Solution { public int minimumJumps(int[] forbidden, int a, int b, int x) { if (x == 0) { return 0; } Set<Integer> forbiddenSet = new HashSet<>(); int maxVal = x; for (int f : forbidden) { forbiddenSet.add(f); maxVal = Math.max(maxVal, f); } // A safe upper bound for position to explore. int limit = maxVal + a + b; Queue<int[]> queue = new LinkedList<>(); // State: {position, isLastJumpBackward} (0 for false, 1 for true) queue.offer(new int[]{0, 0}); // visited[position][isLastJumpBackward] boolean[][] visited = new boolean[limit + 1][2]; visited[0][0] = true; int jumps = 0; while (!queue.isEmpty()) { int levelSize = queue.size(); for (int i = 0; i < levelSize; i++) { int[] current = queue.poll(); int pos = current[0]; int lastWasBackward = current[1]; if (pos == x) { return jumps; } // Try forward jump int forwardPos = pos + a; if (forwardPos <= limit && !visited[forwardPos][0] && !forbiddenSet.contains(forwardPos)) { visited[forwardPos][0] = true; queue.offer(new int[]{forwardPos, 0}); } // Try backward jump (only if last jump was not backward) if (lastWasBackward == 0) { int backwardPos = pos - b; if (backwardPos >= 0 && !visited[backwardPos][1] && !forbiddenSet.contains(backwardPos)) { visited[backwardPos][1] = true; queue.offer(new int[]{backwardPos, 1}); } } } jumps++; } return -1; }}Complexity
Time
O(max(x, max_f) + a + b). Each state `(position, was_backward)` is visited at most once. The number of states is proportional to the search limit.
Space
O(max(x, max_f) + a + b), where `max_f` is the maximum forbidden position. This space is used for the `visited` array and the queue.
Trade-offs
Pros
Guaranteed to find the shortest path in terms of number of jumps.
Efficient and avoids re-computing states by using a
visitedset.Completes within the time limits for the given constraints.
Cons
Requires careful determination of the search space boundary to be both correct and efficient.
Uses more memory than DFS for wide search graphs, although this is not an issue with the given constraints.
Solutions
Solution
class Solution { public int minimumJumps ( int [] forbidden , int a , int b , int x ) { Set < Integer > s = new HashSet <>(); for ( int v : forbidden ) { s . add ( v ); } Deque < int []> q = new ArrayDeque <>(); q . offer ( new int [] { 0 , 1 }); final int n = 6000 ; boolean [][] vis = new boolean [ n ][ 2 ]; vis [ 0 ][ 1 ] = true ; for ( int ans = 0 ; ! q . isEmpty (); ++ ans ) { for ( int t = q . size (); t > 0 ; -- t ) { var p = q . poll (); int i = p [ 0 ], k = p [ 1 ]; if ( i == x ) { return ans ; } List < int []> nxt = new ArrayList <>(); nxt . add ( new int [] { i + a , 1 }); if (( k & 1 ) == 1 ) { nxt . add ( new int [] { i - b , 0 }); } for ( var e : nxt ) { int j = e [ 0 ]; k = e [ 1 ]; if ( j >= 0 && j < n && ! s . contains ( j ) && ! vis [ j ][ k ]) { q . offer ( new int [] { j , k }); vis [ j ][ k ] = true ; } } } } return - 1 ; } }class Solution { public: int minimumJumps ( vector < int >& forbidden , int a , int b , int x ) { unordered_set < int > s ( forbidden . begin (), forbidden . end ()); queue < pair < int , int >> q ; q . emplace ( 0 , 1 ); const int n = 6000 ; bool vis [ n ][ 2 ]; memset ( vis , false , sizeof ( vis )); vis [ 0 ][ 1 ] = true ; for ( int ans = 0 ; q . size (); ++ ans ) { for ( int t = q . size (); t ; -- t ) { auto [ i , k ] = q . front (); q . pop (); if ( i == x ) { return ans ; } vector < pair < int , int >> nxts = { { i + a , 1 } }; if ( k & 1 ) { nxts . emplace_back ( i - b , 0 ); } for ( auto [ j , l ] : nxts ) { if ( j >= 0 && j < n && ! s . count ( j ) && ! vis [ j ][ l ]) { vis [ j ][ l ] = true ; q . emplace ( j , l ); } } } } return - 1 ; } };class Solution : def minimumJumps ( self , forbidden : List [ int ], a : int , b : int , x : int ) -> int : s = set ( forbidden ) q = deque ([( 0 , 1 )]) vis = {( 0 , 1 )} ans = 0 while q : for _ in range ( len ( q )): i , k = q . popleft () if i == x : return ans nxt = [( i + a , 1 )] if k & 1 : nxt . append (( i - b , 0 )) 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 )) ans += 1 return - 1Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.