403. 青蛙过河
难度:困难
语言:java
题目内容
一只青蛙想要过河。 假定河流被等分为若干个单元格,并且在每一个单元格内都有可能放有一块石子(也有可能没有)。 青蛙可以跳上石子,但是不可以跳入水中。
给你石子的位置列表 stones(用单元格序号 升序 表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一块石子上)。
开始时, 青蛙默认已站在第一块石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格 1 跳至单元格 2 )。
如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1 个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。
题目分析
困难题,我没有看答案先自己判断了一下,感觉就是一个dp的题目,能否跳到最后一个,取决于,前面能否有一个满足要求的跳点。
但是设计dp的时候,要考虑以下两种情况:
- 能否跳到当前位置并不是直接由前一个决定(可能跨越了中间的一部分石子)
- 可能有多种情况可以跳跃到当前位置(因为有三种跳跃距离的选择)
设计的dp函数我觉得要包括的点一定是,从上一步跳过来用了多少步,不然没法考虑下面的跳跃长度,所以设计dp 的List包含两项是很容易想到的,一项是代表位置,一项是代表跳跃步数,但是比较巧妙的一个点是,用Boolean型的值,作为dp里面的Value,可以方便求得下面的值
在这里dp[i][k]里的值代表在第j个点,要跳k步才能到达第i个点,
只有跳到j点时,跳了k-1,k,k+1步才能得到k这个选项,
也就是后面三个值,至少有一个是true,是个或的关系
dp[i][k] = dp[j][k-1] || dp[j][k] || dp[j][k+1];
记录一下几个错误
开始的时候我很快就写起来了
这里面有我的两个错误,通过注释标注出来了
class Solution {
public boolean canCross(int[] stones) {
int n = stones.length;
boolean [][] dp = new boolean[n][stones[n-1]+2]; //用例还考虑了int的边界值的情况
dp[0][0] = true;
for (int i =1;i<n;i ++){
for (int j = i-1;j>=0;j--){
int k = stones[i] -stones[j];
dp[i][k] = dp[j][k-1] || dp[j][k] || dp[j][k+1];
if(i==n-1){
return dp[i][k]; //这里如果没有把dp的结果放进去,在第一次到达N-1的时候就不会遍历j了
}
}
}
return false;
}
}
好吧,时间原因,再次借鉴了答案,发现了一下设计中的细节。
- 「现在所处的石子编号」为 i 时,「上一次跳跃距离」k 必定满足 k <=i。
这是因为一次跳跃的距离最多就加1嘛,每次都是+1,也只能跟i相同。 - 当第 i 个石子与第 i-1个石子距离超过 i时,青蛙必定无法到达终点。
因为上面1里面的原因,这一点也就很好理解了
于是再次进行了一些优化,得到了答案的代码
class Solution {
public boolean canCross(int[] stones) {
int n = stones.length;
boolean[][] dp = new boolean[n][n];
dp[0][0] = true;
for (int i = 1; i < n; ++i) {
if (stones[i] - stones[i - 1] > i) {
return false;
//这里就是上文的细节2
}
}
for (int i = 1; i < n; ++i) {
for (int j = i - 1; j >= 0; --j) {
int k = stones[i] - stones[j];
if (k > j + 1) {
break; //这是上文的细节1,也是由于这个存在,dp的第二项只需要设为长度n即可了
}
dp[i][k] = dp[j][k - 1] || dp[j][k] || dp[j][k + 1];
if (i == n - 1 && dp[i][k]) {
// 这里与上面代码的比较,需要加上判断条件而不是直接输出
return true;
}
}
}
return false;
}
}