问题描述:
一只青蛙正要过河,这条河分成了 x 个单位,每个单位可能存在石头,青蛙可以跳到石头上,但它不能跳进水里。
按照顺序给出石头所在的位置,判断青蛙能否到达最后一块石头所在的位置。刚开始时青蛙在第一块石头上,假设青蛙第一次跳只能跳一个单位的长度。
如果青蛙最后一个跳 k 个单位,那么它下一次只能跳 k - 1 ,k 或者 k + 1 个单位。注意青蛙只能向前跳。
注意事项:
石头的个数 >= 2并且 <= 1100。
每块石头的位置是一个非负数并且 < 2^31。
第一块石头的位置总是 0.
样例说明:
给出石头的位置为 [0,1,3,5,6,8,12,17]
总共8块石头。
第一块石头在 0 位置,第二块石头在 1 位置,第三块石头在 3 位置等等......
最后一块石头在 17 位置。
返回 true。青蛙可以通过跳 1 格到第二块石头,跳 2 格到第三块石头,跳 2 格到第四块石头,跳 3 格到第六块石头,跳 4 格到第七块石头,最后跳 5 格到第八块石头。
给出石头的位置为 `[0,1,2,3,4,8,9,11]`
返回 false。青蛙没有办法跳到最后一块石头因为第五块石头跟第六块石头的距离太大了。
问题分析:
1.青蛙每跳到一块石头上面,下一步就有三种选择,
k - 1 ,k 或者 k + 1 个单位
2.当青蛙跳到下一个石头上面的时候,就更加靠近了对岸,然后我们又可以在以下一个石头为起点,以同样的思路分析问题,但是问题的规模更小了,当跳到最后一块石头上面的时候,就结束了。这个思路符合递归算法的本质。
解决代码:
/**
* 2018-04-30
* @author liujie
*
*/
public class TestCanCross {
public boolean canCross(int[] stones){
if (stones == null || stones.length < 2) return true;
//这一步只是为了过滤掉一些明显错误的数据,比如说
//{0,1,2,3,4,5,6,7,8,9,10,.....995,996,997,998,99999999}
//这种数据,某一块和之前一块石头的距离明显过大,就不用放到程序里面验证了,不然太耗时间
for (int i = stones.length - 1; i > 2; i --) {
if (stones[i] > 2 * stones[i - 1]) return false;
}
//假设青蛙总能跳出第一步
return canCross(stones, 1, stones[1]);
}
private boolean canCross(int[] stones, int currentLocation, int lastStep) {
//这是base case, 当青蛙跳到了最后一块石头上面,说明成功了
if (currentLocation == stones.length - 1) return true;
int next = 0;
//能找到下一个步距为lastStep + 1的石头,并且调到测试下一个石头是否能成功
if ((next = findNextLocation(stones, currentLocation, lastStep + 1)) != -1
&& canCross(stones, next, lastStep + 1))
return true;
if ((next = findNextLocation(stones, currentLocation, lastStep)) != -1
&& canCross(stones, next, lastStep))
return true;
if ((next = findNextLocation(stones, currentLocation, lastStep - 1)) != -1
&& canCross(stones, next, lastStep - 1))
return true;
return false;
}
/**
* 找到当前石头后,一个相距为dumpStep的石头
* @param stones
* @param currentLocation 当前石头位置
* @param dumpStep 测试的距离
* @return 如果正好找到一个石头,与当前石头的位置相距为dumpStep,则返回该石头的下标
*/
private int findNextLocation(int[] stones, int currentLocation, int dumpStep) {
//从当前下标开始,一个一个往后移动,寻找相距为dumpStep的下标
for (int i = currentLocation + 1; i < stones.length; i ++){
if (stones[i] - stones[currentLocation] == dumpStep) return i;
//因为是递增的数组,如果这个距离都已经大于dumpStep了,后面就更不可能了
if (stones[i] - stones[currentLocation] > dumpStep) return -1;
}
//找遍数组也没有找到,返回一个默认值
return -1;
}
}
代码细节:
if ((next = findNextLocation(stones, currentLocation, lastStep + 1)) != -1
&& canCross(stones, next, lastStep + 1))
return true;
if ((next = findNextLocation(stones, currentLocation, lastStep)) != -1
&& canCross(stones, next, lastStep))
return true;
if ((next = findNextLocation(stones, currentLocation, lastStep - 1)) != -1
&& canCross(stones, next, lastStep - 1))
return true;
return false;
这一部分是整个代码的核心递归
1.采用的是lastStep + 1, lastStep, lastStep - 1的顺序,主要是为了在三者都符合时,尽量跳大一点的距离
2.采用的是
if ((next = findNextLocation(stones, currentLocation, lastStep + 1)) != -1
&& canCross(stones, next, lastStep + 1))
return true;
而不是
if ((next = findNextLocation(stones, currentLocation, lastStep + 1)) != -1 )
return canCross(stones, next, lastStep + 1);
因为可能找到lastStep + 1的步距石头,但是下一个石头不一定就能成功,但也不一定失败,可能 lastStep或者lastStep - 1就能成功,他们三者就是平等的,