403. Frog Jump

  • 31.0%

https://leetcode.com/problems/frog-jump/

A frog is crossing a river. The river is divided into x 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 is able to cross the river by landing on the last stone. Initially, the frog is on the first stone and assume the first jump must be 1 unit.

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

Note:

  • The number of stones is ≥ 2 and is < 1,100.
  • Each stone’s position will be a non-negative integer < 231.
  • The first stone’s position is always 0.
1
2
3
4
5
6
7
8
9
10
11
12
13
Example 1:

[0,1,3,5,6,8,12,17]

There are a total of 8 stones.
The first stone at the 0th unit, second stone at the 1st unit,
third stone at the 3rd unit, and so on...
The last stone at the 17th unit.

Return true. 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.
1
2
3
4
5
6
Example 2:

[0,1,2,3,4,8,9,11]

Return false. There is no way to jump to the last stone as
the gap between the 5th and 6th stone is too large.

6ms, September 19, 2016

https://discuss.leetcode.com/topic/59439/straight-forward-9ms-7-line-c-solution-with-explanation

方法一:

Straight-forward 9ms 7-line c++ solution with explanation

Search for the last stone in a depth-first way, prune those exceeding the [k-1,k+1] range. Well, I think the code is simple enough and need no more explanation.

1
2
3
4
5
6
7
8
9
bool canCross(vector<int>& stones, int pos = 0, int k = 0) {
for (int i = pos + 1; i < stones.size(); i++) {
int gap = stones[i] - stones[pos];
if (gap < k - 1) continue;
if (gap > k + 1) return false;
if (canCross(stones, i, gap)) return true;
}
return pos == stones.size() - 1;
}

我的代码实现:

上面的代码及本代码都会出现超时错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
bool canCross(vector<int>& stones) {
return helper(stones, 0, 0);
}

// pos当前位置的索引,step位于位置pos的步长,函数表示当前能到达pos,
// 步长为step,返回是否能到达最后一个位置
bool helper(vector<int>& stones, int pos, int step){
for(int i=pos+1; i<stones.size(); i++){
int cur_step = stones[i] - stones[pos];
if(cur_step<step-1) continue;
if(cur_step>step+1) return false;
if(helper(stones, i, cur_step)) return true;
}
return pos==stones.size()-1;
}
};

方法二:

我的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public:
bool canCross(vector<int>& stones) {
unordered_map<int, bool> map;
return helper(stones, 0, 0, map);
}

bool helper(vector<int>& stones, int index, int step, unordered_map<int, bool>& map){
int key = step<<11 | index;
// 一定要先有这句话,不然每次都要重新运行
if(map.count(key)>0)
return map[key];
for(int i=index+1; i<stones.size(); i++){
int gap = stones[i] - stones[index];
if(gap<step-1)
continue;
if(gap>step+1){
map[key] = false;
return map[key];
}
if(helper(stones, i, gap, map)){
map[key] = true;
return map[key];
}
}
map[key] = (index==stones.size()-1);
return map[key];
}
};

This can pass OJ at 9ms but is inefficient for extreme cases. (update: new test cases are added and the solution above no longer passes OJ, please see the solution below which takes 62ms) We can memorize the returns with minimum effort:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unordered_map<int, bool> dp;

bool canCross(vector<int>& stones, int pos = 0, int k = 0) {
int key = pos | k << 11;

if (dp.count(key) > 0)
return dp[key];

for (int i = pos + 1; i < stones.size(); i++) {
int gap = stones[i] - stones[pos];
if (gap < k - 1)
continue;
if (gap > k + 1)
return dp[key] = false;
if (canCross(stones, i, gap))
return dp[key] = true;
}

return dp[key] = (pos == stones.size() - 1);
}

The number of stones is less than 1100 so pos will always be less than 2^11 (2048).
Stone positions could be theoretically up to 2^31 but k is practically not possible to be that big for the parameter as the steps must start from 0 and 1 and at the 1100th step the greatest valid k would be 1100. So combining pos and k is safe here.

我的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
bool canCross(vector<int>& stones) {
unordered_map<int, bool> map;
return helper(stones, 0, 0, map);
}

bool helper(vector<int>& stones, int pos, int step, unordered_map<int, bool>& map){
// map有find也有count函数,count有结果返回1,否则0
// find有则返回一个迭代器,否则返回一个end的迭代器
// key要用自己设置,包含pos和step,只使用pos是不能返回正确结果的
// dp时,不要用pos做key,使用pos的组合做也是可以的。
int key = pos | step << 11;
if(map.count(key)>0)
return map[key];
for(int i=pos+1; i<stones.size(); i++){
int gap = stones[i] - stones[pos];
if(gap<step-1)
continue;
if(gap>step+1){
map[key] = false;
return map[key]; // 此处也不要忘了更新map
}
if(helper(stones, i, gap, map)){
map[key] = true; // 注意更新map,注意更新的是pos,不是i
return map[key];
}
}
// map保存当前位置pos能否到达最后位置
// 经过前面的循环后,保存是否能到达,不要忘记此处的更新
map[key] = (pos == stones.size()-1);
return map[key];
}
};

方法三:

我的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
bool canCross(vector<int>& stones) {
unordered_map<int, set<int>> map = {{0, {0}}}; // 学习map初始化,及set初始化
for(auto pos:stones){
for(auto step:map[pos]){
if(step-1>0) map[pos+step-1].insert(step-1); // insert
if(step>0) map[pos+step].insert(step);
map[pos+step+1].insert(step+1);
}
}
return map[stones.back()].size();
}
};

使用一个map,记录每个位置为key,value为到达当前位置的所有步长的set集合。

平时不怎么用map再套set,这个方法很容易想到,也比较有用。

https://discuss.leetcode.com/topic/60194/c-9-lines-o-n-2-iterative-dp-solution

C++ 9 lines O(n^2) iterative DP solution

This solution is not as fast as some other O(n^2) DFS solutions possibly due to the OJ test cases. But it’s simple and clean.
Special thanks @farahcs and @vesion for correcting the bug in previous code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool canCross(vector<int>& stones) {
// To record available last steps to reach current position. Position 0 need 0 step to be reached
unordered_map<int, unordered_set<int>> steps = {{0, {0}}};

for (int pos : stones) {
for (auto it = steps[pos].begin(); it != steps[pos].end(); it++) { // record all future reachable positions
if (*it - 1) { steps[pos + *it - 1].insert(*it - 1); }
steps[pos + *it].insert(*it);
steps[pos + *it + 1].insert(*it + 1);
}
}

return steps[stones.back()].size(); // check if the last position is reachable
}

python


https://discuss.leetcode.com/topic/60080/python-dfs-easy-understanding-using-memo

Python DFS easy understanding using memo

Following is my backtracking solution using dict for memorization.

The memo dict is using for save those dead end. So when we get to the same stone with the same speed we don’t need to search further.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution(object):
def canCross(self, stones):
self.memo = set()
target = stones[-1]
stones = set(stones)

res = self.bt(stones, 1, 1, target)
return res

def bt(self, stones, cur, speed, target):
# check memo
if (cur, speed) in self.memo:
return False

if cur==target:
return True

if cur>target or cur<0 or speed<=0 or cur not in stones:
return False
# dfs
candidate = [speed-1, speed, speed+1]
for c in candidate:
if (cur + c) in stones:
if self.bt(stones, cur+c, c, target):
return True

self.memo.add((cur,speed))
return False

java


26ms, September 19, 2016

https://discuss.leetcode.com/topic/59337/easy-version-java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Solution {
public boolean canCross(int[] stones) {
if(stones[1]> 1) return false;
if(stones.length == 2) return true;
return helper(stones, 1, 1);
}

private boolean helper(int[] arr, int i, int step){
boolean pass = false;
if(i == arr.length -1) return true;
for(int j = i+1; j < arr.length; j++){
if(arr[j] <= arr[i] + step+1 && arr[j] >= arr[i] + step-1)
pass = pass || helper(arr, j, arr[j] - arr[i]);
}
return pass;
}
}

https://leetcode.com/articles/frog-jump/

Approach #3 Using Memorization [Accepted]

Algorithm

Another problem with above approaches is that we can make the same function calls coming through different paths e.g. For a given currentIndexcurrentIndex, we can call the recursive function canCrosscanCross with the jumpsizejumpsize, say nn. This nn could be resulting from previous jumpsizejumpsize being n-1n−1,nn or n+1n+1. Thus, many redundant function calls could be made prolonging the running time. This redundancy can be removed by making use of memorization. We make use of a 2-d memomemo array, initialized by -1−1s, to store the result returned from a function call for a particular currentIndexcurrentIndex and jumpsizejumpsize. If the same currentIndexcurrentIndex and jumpsizejumpsize happens is encountered again, we can return the result directly using the memomemo array. This helps to prune the search tree to a great extent.

java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Solution {
public boolean canCross(int[] stones) {
int[][] memo = new int[stones.length][stones.length];
for (int[] row : memo) {
Arrays.fill(row, -1);
}
return can_Cross(stones, 0, 0, memo) == 1;
}
public int can_Cross(int[] stones, int ind, int jumpsize, int[][] memo) {
if (memo[ind][jumpsize] >= 0) {
return memo[ind][jumpsize];
}
for (int i = ind + 1; i < stones.length; i++) {
int gap = stones[i] - stones[ind];
if (gap >= jumpsize - 1 && gap <= jumpsize + 1) {
if (can_Cross(stones, i, gap, memo) == 1) {
memo[ind][gap] = 1;
return 1;
}
}
}
memo[ind][jumpsize] = (ind == stones.length - 1) ? 1 : 0;
return memo[ind][jumpsize];
}
}

Approach #4 Using Memorization with Binary Search [Accepted]

Algorithm

We can optimize the above memorization approach, if we make use of Binary Search to find if a stone exists at currentPostion + newjumpsizecurrentPostion+newjumpsize instead of searching linearly.

java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Solution {
public boolean canCross(int[] stones) {
int[][] memo = new int[stones.length][stones.length];
for (int[] row : memo) {
Arrays.fill(row, -1);
}
return can_Cross(stones, 0, 0, memo) == 1;
}
public int can_Cross(int[] stones, int ind, int jumpsize, int[][] memo) {
if (memo[ind][jumpsize] >= 0) {
return memo[ind][jumpsize];
}
int ind1 = Arrays.binarySearch(stones, ind + 1, stones.length, stones[ind] + jumpsize);
if (ind1 >= 0 && can_Cross(stones, ind1, jumpsize, memo) == 1) {
memo[ind][jumpsize] = 1;
return 1;
}
int ind2 = Arrays.binarySearch(stones, ind + 1, stones.length, stones[ind] + jumpsize - 1);
if (ind2 >= 0 && can_Cross(stones, ind2, jumpsize - 1, memo) == 1) {
memo[ind][jumpsize - 1] = 1;
return 1;
}
int ind3 = Arrays.binarySearch(stones, ind + 1, stones.length, stones[ind] + jumpsize + 1);
if (ind3 >= 0 && can_Cross(stones, ind3, jumpsize + 1, memo) == 1) {
memo[ind][jumpsize + 1] = 1;
return 1;
}
memo[ind][jumpsize] = ((ind == stones.length - 1) ? 1 : 0);
return memo[ind][jumpsize];
}
}

Approach #5 Using Dynamic Programming[Accepted]

Algorithm

In the DP Approach, we make use of a hashmap mapmap which contains key:valuekey:value pairs such that keykey refers to the position at which a stone is present and valuevalue is a set containing the jumpsizejumpsize which can lead to the current stone position. We start by making a hashmap whose keykeys are all the positions at which a stone is present and the valuevalues are all empty except position 0 whose value contains 0. Then, we start traversing the elements(positions) of the given stone array in sequential order. For the currentPositioncurrentPosition, for every possible jumpsizejumpsize in the valuevalue set, we check if currentPosition + newjumpsizecurrentPosition+newjumpsize exists in the mapmap, where newjumpsizenewjumpsize can be either jumpsize-1jumpsize−1, jumpsizejumpsize, jumpsize+1jumpsize+1. If so, we append the corresponding valuevalue set with newjumpsizenewjumpsize. We continue in the same manner. If at the end, the valuevalue set corresponding to the last position is non-empty, we conclude that reaching the end is possible, otherwise, it isn’t.

For more understanding see this animation-

java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Solution {
public boolean canCross(int[] stones) {
HashMap<Integer, Set<Integer>> map = new HashMap<>();
for (int i = 0; i < stones.length; i++) {
map.put(stones[i], new HashSet<Integer>());
}
map.get(0).add(0);
for (int i = 0; i < stones.length; i++) {
for (int k : map.get(stones[i])) {
for (int step = k - 1; step <= k + 1; step++) {
if (step > 0 && map.containsKey(stones[i] + step)) {
map.get(stones[i] + step).add(step);
}
}
}
}
return map.get(stones[stones.length - 1]).size() > 0;
}
}

谢谢你,可爱的朋友。