2498. Frog Jump II
Problem Description
You have a frog that needs to make a round trip across a river using stepping stones. The stones are represented by an array stones
where each element indicates the position of a stone, and the array is sorted in strictly increasing order.
The frog starts at the first stone (index 0) and must:
- Travel to the last stone
- Return back to the first stone
Key constraints:
- The frog can use each stone at most once during the entire round trip
- This means if the frog jumps on a stone while going forward, it cannot use that same stone on the way back
Jump length and cost:
- The length of a jump between two stones is the absolute difference between their positions:
|stones[i] - stones[j]|
- The cost of the entire path is defined as the maximum jump length among all jumps in the complete round trip
Your task is to find the minimum possible cost for the frog to complete this round trip.
Example visualization:
If stones = [0, 2, 5, 6, 7]
, the frog might:
- Go forward: 0 → 2 → 5 → 7 (jumps of length 2, 3, 2)
- Return: 7 → 6 → 0 (jumps of length 1, 6)
- Maximum jump length = 6, so the cost = 6
The goal is to find a path strategy that minimizes this maximum jump length across all possible valid round-trip paths.
Intuition
The key insight is to think about what happens when we try to minimize the maximum jump length. Since each stone can only be used once in the entire round trip, we need to strategically decide which stones to use on the forward path and which to save for the return path.
Consider this: if we visit every single stone on the way forward, we'd have no stones left for the return journey except to jump directly from the last stone back to the first stone. This would result in a very large jump of stones[n-1] - stones[0]
.
Instead, we should alternate which stones we use. A clever strategy is to:
- On the forward path: visit stones at even indices (0, 2, 4, ...)
- On the return path: visit stones at odd indices (..., 5, 3, 1)
This alternating pattern ensures that:
- We have stones available for both directions
- The jumps are relatively balanced in length
With this pattern, the forward jumps would be from stone i
to stone i+2
(skipping one stone each time). The return jumps would also skip one stone each time but use the previously skipped stones.
The largest jump in this strategy would be:
- Either a forward jump:
stones[i+2] - stones[i]
for some eveni
- Or the initial jump if we start directly to stone 1:
stones[1] - stones[0]
- Or the final return jump to stone 0
By checking all consecutive pairs of stones with distance 2 apart (stones[i] - stones[i-2]
), along with the first jump stones[1] - stones[0]
, we can find the maximum jump length in this optimal alternating pattern. This gives us the minimum possible cost since any other pattern would either create larger jumps or leave us with no valid return path.
Learn more about Greedy and Binary Search patterns.
Solution Approach
The implementation is remarkably simple once we understand the alternating stone pattern strategy:
def maxJump(self, stones: List[int]) -> int:
ans = stones[1] - stones[0]
for i in range(2, len(stones)):
ans = max(ans, stones[i] - stones[i - 2])
return ans
Step-by-step breakdown:
-
Initialize with the first jump:
ans = stones[1] - stones[0]
- This handles the edge case where we might jump directly from stone 0 to stone 1
- This is also the minimum jump we must consider since we need to leave the first stone
-
Check all jumps that skip one stone: Loop through indices starting from 2
- For each stone at index
i
, calculate the jump distance from stonei-2
to stonei
- This represents jumps like: stone 0→2, stone 1→3, stone 2→4, etc.
- Update
ans
to keep track of the maximum jump distance encountered
- For each stone at index
-
Return the maximum jump distance found
- This maximum represents the minimum possible cost for the round trip
Why this works:
The algorithm implicitly constructs the optimal path:
- Forward path: 0 → 2 → 4 → 6 → ... → (last or second-to-last stone)
- Return path: ... → 5 → 3 → 1 → 0
The jumps we're checking (stones[i] - stones[i-2]
) cover:
- All forward jumps in the even-indexed path
- All return jumps in the odd-indexed path (since jumping from odd index
j
toj-2
has the same distance as jumping fromj-2
toj
)
Time Complexity: O(n)
where n is the number of stones - we iterate through the array once
Space Complexity: O(1)
- we only use a constant amount of extra space to store the answer
This elegant solution leverages the sorted nature of the stones array and the symmetry of the round-trip requirement to find the optimal jumping pattern with minimal code.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with stones = [0, 3, 9, 10, 12]
.
Step 1: Initialize with first possible jump
ans = stones[1] - stones[0] = 3 - 0 = 3
- This represents a potential jump from stone 0 to stone 1
Step 2: Check all jumps that skip one stone
-
i = 2
: Check jump from stone 0 to stone 2- Jump length:
stones[2] - stones[0] = 9 - 0 = 9
- Update:
ans = max(3, 9) = 9
- Jump length:
-
i = 3
: Check jump from stone 1 to stone 3- Jump length:
stones[3] - stones[1] = 10 - 3 = 7
- Update:
ans = max(9, 7) = 9
- Jump length:
-
i = 4
: Check jump from stone 2 to stone 4- Jump length:
stones[4] - stones[2] = 12 - 9 = 3
- Update:
ans = max(9, 3) = 9
- Jump length:
Result: The minimum possible cost is 9
Verifying with the actual path: This algorithm implicitly creates the alternating pattern:
- Forward path: 0 → 2 → 4 (jumps: 9, 3)
- Return path: 4 → 3 → 1 → 0 (jumps: 2, 7, 3)
All jumps in the complete round trip: [9, 3, 2, 7, 3] Maximum jump = 9 ✓
Notice how the algorithm found this optimal cost by checking:
- The jump 0→2 (length 9) - which appears in our forward path
- The jump 1→3 (length 7) - which when reversed (3→1) appears in our return path
- The jump 2→4 (length 3) - which appears in our forward path
The algorithm correctly identifies that the maximum jump of 9 cannot be reduced further while maintaining a valid round trip where each stone is used at most once.
Solution Implementation
1class Solution:
2 def maxJump(self, stones: List[int]) -> int:
3 # Initialize the maximum jump distance with the first jump (index 0 to 1)
4 max_jump_distance = stones[1] - stones[0]
5
6 # Iterate through the stones starting from index 2
7 # We check jumps that skip one stone (i-2 to i)
8 for i in range(2, len(stones)):
9 # Update max jump distance if current jump is larger
10 # Jump from stone at index i-2 to stone at index i
11 max_jump_distance = max(max_jump_distance, stones[i] - stones[i - 2])
12
13 return max_jump_distance
14
1class Solution {
2 public int maxJump(int[] stones) {
3 // Initialize the maximum jump distance with the first jump (index 0 to 1)
4 int maxJumpDistance = stones[1] - stones[0];
5
6 // Iterate through the array starting from index 2
7 // We check jumps of length 2 (skipping one stone)
8 for (int i = 2; i < stones.length; i++) {
9 // Calculate the jump distance from stone at index (i-2) to stone at index i
10 // This represents jumping over one stone
11 int currentJumpDistance = stones[i] - stones[i - 2];
12
13 // Update the maximum jump distance if current jump is larger
14 maxJumpDistance = Math.max(maxJumpDistance, currentJumpDistance);
15 }
16
17 // Return the maximum jump distance found
18 return maxJumpDistance;
19 }
20}
21
1class Solution {
2public:
3 int maxJump(vector<int>& stones) {
4 // Initialize the answer with the first jump distance (from stone 0 to stone 1)
5 int maxJumpDistance = stones[1] - stones[0];
6
7 // Iterate through stones starting from index 2
8 // Calculate the maximum jump distance when skipping one stone
9 for (int i = 2; i < stones.size(); ++i) {
10 // Compare current max with the jump from stone (i-2) to stone i
11 // This represents jumping over stone (i-1)
12 maxJumpDistance = max(maxJumpDistance, stones[i] - stones[i - 2]);
13 }
14
15 return maxJumpDistance;
16 }
17};
18
1function maxJump(stones: number[]): number {
2 // Initialize the answer with the first jump distance (from stone 0 to stone 1)
3 let maxJumpDistance: number = stones[1] - stones[0];
4
5 // Iterate through stones starting from index 2
6 // Calculate the maximum jump distance when skipping one stone
7 for (let i: number = 2; i < stones.length; i++) {
8 // Compare current max with the jump from stone (i-2) to stone i
9 // This represents jumping over stone (i-1)
10 maxJumpDistance = Math.max(maxJumpDistance, stones[i] - stones[i - 2]);
11 }
12
13 return maxJumpDistance;
14}
15
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the stones array. The algorithm iterates through the stones array once, starting from index 2 to the end. Each iteration performs a constant-time operation (calculating the difference between stones[i]
and stones[i-2]
and comparing it with the current maximum). Therefore, the overall time complexity is linear.
Space Complexity: O(1)
. The algorithm uses only a constant amount of extra space. It maintains a single variable ans
to track the maximum jump distance throughout the iteration. No additional data structures that scale with the input size are created, resulting in constant space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Optimal Strategy
Many people initially try complex approaches like dynamic programming or graph algorithms, thinking they need to explicitly track which stones are used in forward vs. return paths. The key insight that's often missed is that the alternating pattern (even indices forward, odd indices back) is always optimal.
Why this happens: The problem statement makes it seem like we need to find two completely separate paths, leading to overthinking.
Solution: Recognize that with sorted stones, the alternating pattern minimizes the maximum jump by distributing stones evenly between forward and return paths.
2. Forgetting Edge Cases with Small Arrays
When stones
has only 2 elements [a, b]
, the frog must jump directly from a
to b
and back, making the cost b - a
. The algorithm handles this correctly by initializing with stones[1] - stones[0]
, but it's easy to overlook why this initialization is crucial.
Example pitfall code:
def maxJump(self, stones: List[int]) -> int:
ans = 0 # Wrong initialization!
for i in range(2, len(stones)):
ans = max(ans, stones[i] - stones[i - 2])
return ans # Returns 0 for 2-element arrays!
Solution: Always initialize with stones[1] - stones[0]
to handle the direct jump case.
3. Incorrectly Trying to Handle the Last Stone Separately
Some might think the last stone needs special handling since the frog must reach it. They might add extra logic to ensure the last stone is included in the forward path.
Incorrect approach:
def maxJump(self, stones: List[int]) -> int:
ans = stones[1] - stones[0]
for i in range(2, len(stones) - 1):
ans = max(ans, stones[i] - stones[i - 2])
# Special case for last stone - UNNECESSARY!
ans = max(ans, stones[-1] - stones[-2])
return ans
Solution: The original algorithm already handles this correctly. If there's an even number of stones, the last stone is naturally included in the even-indexed path. If odd, the second-to-last stone jumps to the last stone as part of the pattern.
4. Attempting to Track Used Stones Explicitly
Some might create additional data structures to track which stones have been used:
Overcomplicated approach:
def maxJump(self, stones: List[int]) -> int:
used = [False] * len(stones)
# Complex logic to track forward and return paths...
Solution: The beauty of this problem is that the alternating pattern implicitly ensures no stone is used twice. No explicit tracking is needed.
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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!