Frog Jump

HARD

Description

A frog is crossing a river. The river is divided into some number of units, and at each unit, there may or may not exist a stone. The frog can jump on a stone, but it must not jump into the water.

Given a list of stones positions (in units) in sorted ascending order, determine if the frog can cross the river by landing on the last stone. Initially, the frog is on the first stone and assumes the first jump must be 1 unit.

If the frog's last jump was k units, its next jump must be either k - 1, k, or k + 1 units. The frog can only jump in the forward direction.

 

Example 1:

Input: stones = [0,1,3,5,6,8,12,17]
Output: true
Explanation: The frog can jump to the last stone by jumping 1 unit to the 2nd stone, then 2 units to the 3rd stone, then 2 units to the 4th stone, then 3 units to the 6th stone, 4 units to the 7th stone, and 5 units to the 8th stone.

Example 2:

Input: stones = [0,1,2,3,4,8,9,11]
Output: false
Explanation: There is no way to jump to the last stone as the gap between the 5th and 6th stone is too large.

 

Constraints:

  • 2 <= stones.length <= 2000
  • 0 <= stones[i] <= 231 - 1
  • stones[0] == 0
  • stones is sorted in a strictly increasing order.

Approaches

Checkout 3 different approaches to solve Frog Jump. Click on different approaches to view the approach and algorithm in detail.

Iterative Dynamic Programming with Hashing

This approach uses a bottom-up dynamic programming strategy, which is often implemented iteratively. It can be thought of as a Breadth-First Search (BFS) on the state space. We build up the solution by finding all possible jump sizes that can reach each stone, starting from the first stone.

Algorithm

  • Create a HashMap<Integer, Set<Integer>> to store, for each stone position, the set of jump sizes that can be made from it.
  • Initialize the map with all stone positions and empty sets.
  • Add a jump of size 1 to the set for the first stone (at position 0): map.get(0).add(1).
  • Iterate through the stones array from i = 0 to n-1.
  • For the current stone stones[i], get its set of possible jumps from the map.
  • For each jump k in the set:
    • Calculate nextPos = stones[i] + k.
    • If nextPos is the position of the last stone, return true.
    • If there is a stone at nextPos (check using the map), update the set for nextPos by adding k-1 (if > 0), k, and k+1.
  • If the loops complete without reaching the last stone, return false.

We use a HashMap where the keys are the stone positions and the values are Sets of integers. map.get(stone_pos) will store the set of jump sizes that can be made from the stone at stone_pos.

First, we initialize the map for all stone positions with empty sets. The frog starts at stones[0] (position 0) and must make a jump of size 1. So, we initialize map.get(0).add(1). We then iterate through the stones in their given order. For each stone, we look at the set of possible jump sizes k from it. For each jump size k, we calculate the next_position = stone + k. If a stone exists at next_position, we update its entry in the map. Since the last jump was k, the next jump from next_position can be k-1, k, or k+1. We add these valid (positive) jump sizes to the set for next_position.

After iterating through all stones up to the second to last, if we have managed to schedule a jump to the last stone, we return true. If the entire process completes and the last stone was never reached, it's impossible.

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class Solution {
    public boolean canCross(int[] stones) {
        Map<Integer, Set<Integer>> map = new HashMap<>();
        for (int stone : stones) {
            map.put(stone, new HashSet<>());
        }
        
        // First jump is 1 unit from stone 0.
        // This requires a stone at position 1.
        if (!map.containsKey(1) || stones[1] != 1) {
            return false;
        }

        map.get(0).add(1);

        for (int i = 0; i < stones.length; i++) {
            int currentStone = stones[i];
            Set<Integer> jumps = map.get(currentStone);
            for (int k : jumps) {
                int nextPos = currentStone + k;
                if (nextPos == stones[stones.length - 1]) {
                    return true;
                }
                if (map.containsKey(nextPos)) {
                    Set<Integer> nextJumps = map.get(nextPos);
                    if (k - 1 > 0) {
                        nextJumps.add(k - 1);
                    }
                    nextJumps.add(k);
                    nextJumps.add(k + 1);
                }
            }
        }

        return false;
    }
}

Complexity Analysis

Time Complexity: O(N^2). We iterate through `N` stones. For each stone `i`, the number of jumps in its set is at most `i`. The total number of jump calculations is the sum of `i` from 0 to `N-1`, which is `O(N^2)`. Map operations are on average O(1).Space Complexity: O(N^2). The map stores `N` keys. The total number of elements across all sets can be up to `O(N^2)` in the worst case, as the number of possible jump sizes to reach stone `i` can be `O(i)`.

Pros and Cons

Pros:
  • Generally more efficient in practice than recursion due to avoiding call stack overhead.
  • It's a systematic, level-by-level (stone-by-stone) exploration of reachable states.
Cons:
  • Can use significant memory, O(N^2), similar to the memoization approach.

Code Solutions

Checking out 3 solutions in different languages for Frog Jump. Click on different languages to view the code.

class Solution { private Boolean [][] f ; private Map < Integer , Integer > pos = new HashMap <>(); private int [] stones ; private int n ; public boolean canCross ( int [] stones ) { n = stones . length ; f = new Boolean [ n ][ n ]; this . stones = stones ; for ( int i = 0 ; i < n ; ++ i ) { pos . put ( stones [ i ], i ); } return dfs ( 0 , 0 ); } private boolean dfs ( int i , int k ) { if ( i == n - 1 ) { return true ; } if ( f [ i ][ k ] != null ) { return f [ i ][ k ]; } for ( int j = k - 1 ; j <= k + 1 ; ++ j ) { if ( j > 0 ) { int h = stones [ i ] + j ; if ( pos . containsKey ( h ) && dfs ( pos . get ( h ), j )) { return f [ i ][ k ] = true ; } } } return f [ i ][ k ] = false ; } }

Video Solution

Watch the video walkthrough for Frog Jump



Patterns:

Dynamic Programming

Data Structures:

Array

Companies:

Subscribe to Scale Engineer newsletter

Learn about System Design, Software Engineering, and interview experiences every week.

No spam, unsubscribe at any time.