Taro Logo

Frog Jump

Hard
Google logo
Google
1 view
Topics:
ArraysDynamic Programming

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.

For example:

stones = [0,1,3,5,6,8,12,17] should return true because 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.

However, stones = [0,1,2,3,4,8,9,11] should return false because there is no way to jump to the last stone as the gap between the 5th and 6th stone is too large.

Write a function to determine if the frog can cross the river by landing on the last stone, given the stones' positions.

Solution


Frog Jump Problem

Problem 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.

Naive Approach (Recursion)

A brute-force approach would involve exploring all possible jump combinations using recursion. Starting from the first stone, we can try all possible jump lengths (k-1, k, k+1) and recursively check if we can reach the last stone. This approach has exponential time complexity because we may recompute the same subproblems multiple times.

def can_cross_recursive(stones):
    def can_cross(current_position, last_jump):
        if current_position == stones[-1]:
            return True
        
        for next_jump in [last_jump - 1, last_jump, last_jump + 1]:
            if next_jump > 0:
                next_position = current_position + next_jump
                if next_position in stones and next_position > current_position:
                    if can_cross(next_position, next_jump):
                        return True
        return False

    if stones[1] != 1:
        return False

    return can_cross(1, 1)
  • Time Complexity: O(3N), where N is the number of stones. In the worst case, for each stone, we have three possible jump lengths.
  • Space Complexity: O(N) due to the recursion depth.

Optimal Approach (Dynamic Programming)

We can optimize the solution using dynamic programming. We use a hash map where the keys are the stone positions, and the values are sets of possible jump sizes that can lead to that stone. This allows us to store and reuse the results of subproblems. For each stone, we iterate through the possible jump sizes and check if we can reach the next stone.

def can_cross_dp(stones):
    stone_positions = {stone: set() for stone in stones}
    stone_positions[0].add(0)

    for stone in stones:
        for jump_size in stone_positions[stone]:
            for next_jump in [jump_size - 1, jump_size, jump_size + 1]:
                if next_jump > 0 and stone + next_jump in stone_positions:
                    stone_positions[stone + next_jump].add(next_jump)

    return len(stone_positions[stones[-1]]) > 0
  • Time Complexity: O(N2), where N is the number of stones. We iterate through each stone and, for each stone, iterate through possible jump sizes.
  • Space Complexity: O(N2) in the worst case, where each stone has a different set of jump sizes that can reach it. The size of the sets associated with each stone can be up to N. Therefore the total space is potentially N * N = N2.

Edge Cases

  • If the second stone is not at position 1, the frog cannot make the initial jump.
  • If the gap between any two consecutive stones is too large, the frog cannot cross.
  • The input array stones should be sorted in strictly ascending order.

Code (Dynamic Programming)

def can_cross(stones):
    if stones[1] != 1:
        return False

    stone_positions = {stone: set() for stone in stones}
    stone_positions[0].add(0)

    for stone in stones:
        for jump_size in stone_positions[stone]:
            for next_jump in [jump_size - 1, jump_size, jump_size + 1]:
                if next_jump > 0 and stone + next_jump in stone_positions:
                    stone_positions[stone + next_jump].add(next_jump)

    return len(stone_positions[stones[-1]]) > 0