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.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.Could you provide an efficient algorithm to solve this problem?
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.
The most straightforward approach is to use recursion to explore all possible jump combinations. Starting from the first stone, we explore jumps of size k-1
, k
, and k+1
where k
is the size of the previous jump. If any of these jumps leads us to the last stone, we return true
. If we reach a point where no valid jump is possible, we return false
.
Code (Python):
def can_cross_recursive(stones):
def solve(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:
if next_position > current_position and solve(next_position, next_jump):
return True
return False
if stones[1] != 1:
return False
return solve(1, 1)
Time Complexity: O(3N) in the worst case, where N is the number of stones. This is because at each stone, we explore up to 3 possible jump lengths.
Space Complexity: O(N) due to the maximum depth of the recursion call stack.
This naive approach will likely result in a "Time Limit Exceeded" error for larger input sizes.
We can optimize the brute force approach by using dynamic programming (memoization) to avoid redundant calculations. The key idea is to store the results of subproblems so that we can reuse them later. We can use a dictionary or a 2D array to store the results. The state of the subproblem can be defined by the current stone index and the last jump size.
Code (Python):
def can_cross_dp(stones):
n = len(stones)
stone_map = {stone: i for i, stone in enumerate(stones)}
dp = {} # (stone_index, last_jump) -> boolean
def solve(stone_index, last_jump):
if stone_index == n - 1:
return True
if (stone_index, last_jump) in dp:
return dp[(stone_index, last_jump)]
for next_jump in [last_jump - 1, last_jump, last_jump + 1]:
if next_jump > 0:
next_stone = stones[stone_index] + next_jump
if next_stone in stone_map:
next_index = stone_map[next_stone]
if next_index > stone_index:
if solve(next_index, next_jump):
dp[(stone_index, last_jump)] = True
return True
dp[(stone_index, last_jump)] = False
return False
if stones[1] != 1:
return False
return solve(1, 1)
Time Complexity: O(N2), where N is the number of stones. We iterate through each stone and possible jump sizes.
Space Complexity: O(N2) because of the dp
dictionary which at worse can store a result for each combination of stone index and possible jump size.
The dynamic programming approach provides a much more efficient solution than the brute-force recursive approach. By storing the results of subproblems, we significantly reduce the number of calculations required.