题目描述
一只青蛙想过河。 假定河被等分为若干单元格,且在每一个单元格内都有可能放有一块石子(也有可能没有)。 青蛙可以选择跳上石子,但是不可以跳入水中。
给你石子的位置列表 stones(用单元格序号 升序 表示), 请判断青蛙能否成功过河(即能否在最后一步跳至最后一块石子)。
开始时, 青蛙默认站在第一块石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格 1 跳至单元格 2 )。
如果青蛙上一步跳了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1 个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。
样例
示例 1:
输入:stones = [0,1,3,5,6,8,12,17]
输出:true
解释:青蛙能成功过河,按照如下方案跳跃:跳 1 个单位到第 2 块石子, 然后跳 2 个单位到第 3 块石子, 接着 跳 2 个单位到第 4 块石子, 然后跳 3 个单位到第 6 块石子, 跳 4 个单位到第 7 块石子, 最后,跳 5 个单位到第 8 个石子(即最后一块石子)。
示例 2:
输入:stones = [0,1,2,3,4,8,9,11]
输出:false
解释:这是因为第 5 和第 6 个石子之间的间距太大,没有可选的方案供青蛙跳跃过去。
思路
- 这题使用一个二维的 dp 求解,其中 dp[ i ] [ j ] 表示第 i 个石子能否通过上一步距离为 j 的跳跃到达。 最后如果有任何 dp [n-1] [x] 为1,表示可以到达终点,否则无法到达终点。
- 考虑一下这个数组的维度。第一维容易理解,设 stones 数组长度为 n,那么第一维的大小就是 n。对于第二维,两个石子之间的间距可能会非常大,这样的话第二维不是应该非常大吗? 这里有一个技巧,我们知道,青蛙从第 0 个石子跳到 第 1 个石子,长度为1,后面每往后跳一步,跳跃的长度最多只能 +1,所以如果有 n 个石头,其最大跳跃长度也不会超过 n - 1,所以我们第二维大小也开辟为 n。
- 边界条件:dp [0] [0] = 1,表示在 0个石子处,上一步跳跃为 0 的情况下可以到达。这里第二维设置为 0 是因为从 0 石子到 1 石子的步长固定为 1.
- 然后用一个两层循环来更新 dp 数组。第一层循环 i 从 0 到 len - 1遍历石子。 第二层循环 j 从 i -1 到 0 遍历可能的前一个石子。我们将石子 i 和石子 j 之间的间距记为 tmp,如果石子 j 能够跳到石子 i,那么只要 dp [ j ] [tmp]、dp [ j ] [tmp-1]、dp [ j ] [tmp+1] 任意一个值为 1就可以了,满足这个条件的话,我们就可以更新 dp [i] [tmp]为 1.
- 注意,上一步有一个小技巧,在遍历 j 的时候,如果 tmp - 1 > j,那么必然不可能从 j 跳到 i。根据之前的分析,石子 x 的上一步最大跳跃长度不会超过 x,如果 tmp - 1 > j,那么说明 j 石头跳跃 tmp - 1个距离都做不到,更别说跳跃 tmp 或 tmp + 1个距离了,所以这种情况下 j 不能跳到 i。 由于我们是从后往前遍历的 j,所以,接下来的所有 j 都不能跳到 i(因为接下来 j 会越来越小,而 tmp 距离会越来越大),我们可以跳出本轮循环,去进行下一个 i 的循环。
- 由于两层循环,所以时间复杂度为O ( n^2 )
代码
class Solution {
public boolean canCross(int[] stones) {
int len = stones.length;
//dp[i][j]表示,第i个石头能否通过上一步长度为j的跳跃到达
int [][] dp = new int [len][len];
//边界条件
dp[0][0] = 1;
for(int i = 0;i < len;i++) {
//遍历之前的石头
for(int j = i-1;j >= 0;j--) {
//j石头到i石头的距离
int tmp = stones[i] - stones[j];
if(tmp - 1 > j)
break;
if(dp[j][tmp-1] == 1 || dp[j][tmp] == 1 || dp[j][tmp + 1] == 1) {
dp[i][tmp] = 1;
if(i == len-1)
return true;
}
}
}
return false;
}
}