题目链接
题目描述
注意点
- 2 <= stones.length <= 2000
- 0 <= stones[i] <= 2^31 - 1
- stones[0] == 0
- stones按严格升序排列
- 如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1 个单位
- 青蛙只能向前方(终点的方向)跳跃
解答思路
- 初始想到的是使用深度优先遍历,传入上一次到达的位置以及到达上一个位置跳跃的单位,再根据当前能跳跃的单位遍历判断能否跳跃到后续的石子位置,直到找到有一条路径能够到达最后一块石子返回true,如果没有则返回false。但时间复杂度过高,主要原因是每次根据当前能跳跃的单位遍历判断能否跳跃到后续石子位置时有大量重复的计算
- 为了减少重复的计算,首先考虑的是将石子所处的单元格及其对应的第几块石子存储到map中,这样只需要根据stones[idx] + curStep是否在map中即可就能判断当前跳跃的单位能否到达后续的石子(同时也能知道跳到了第几块石子),但是仍然超时。还可以用另一个unreachMap存储以step个单位到达第idx块石子时无法到达最后一块石子,其中key为"idx_step",这样如果后续再次以step个单位到达第idx块石子时不需要再遍历判断,直接返回false即可,加上缓存后不再超时
- 另一个思路是使用动态规划,dp[i][k]表示跳跃k步能否到达第i块石子,从第二块石子开始跳跃(此时stones[1] = 1,dp[1][1] = true),对于任意一块石子i,其前面的所有石子都有可能到达i,需要的步数为k = stones[i] - stones[j],dp[i][k] = dp[j][k - 1] || dp[j][k] || dp[j][k + 1],不断遍历判断能否到达最后一块石子即可
代码
方法一(记忆化搜索):
class Solution {
int n;
Map<Integer, Integer> map;
Map<String, Boolean> unreachMap;
public boolean canCross(int[] stones) {
n = stones.length;
if (stones[1] != 1) {
return false;
}
// key对应单元格,value对应第idx块石子
map = new HashMap<>();
for (int i = 0; i < n; i++) {
map.put(stones[i], i);
}
// key-value对应以step个单位到达第idx块石子时无法到达最后一块石子
unreachMap = new HashMap<>();
return dfs(stones, 1, 1);
}
public boolean dfs(int[] stones, int idx, int preStep) {
if (idx == stones.length - 1) {
return true;
}
String key = idx + "_" + preStep;
if (unreachMap.containsKey(key)) {
return false;
}
for (int i = -1; i <= 1; i++) {
int curStep = preStep + i;
// 只能向前方跳跃
if (curStep <= 0) {
continue;
}
if (map.containsKey(stones[idx] + curStep)) {
Integer newIdx = map.get(stones[idx] + curStep);
if (dfs(stones, newIdx, curStep)) {
return true;
}
}
}
unreachMap.put(key, false);
return false;
}
}
方法二(动态规划):
class Solution {
public boolean canCross(int[] stones) {
int n = stones.length;
if (stones[1] != 1) {
return false;
}
if (n == 2) {
return true;
}
// dp[i][j]表示跳跃j步能否到达第i块石子
boolean[][] dp = new boolean[n + 1][n + 1];
dp[1][1] = true;
for (int i = 2; i < n; i++) {
for (int j = 1; j < i; j++) {
int k = stones[i] - stones[j];
// 由第j个石子往前跳跃不可能跳超过j + 1个单位
if (k > j + 1) {
continue;
}
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;
}
}
关键点
- 记忆化搜索需要缓存经过step个单位到达第idx个石子能否到达最后一块石子
- 动态规划的思想
- 确定dp的大小为new int[n + 1][n + 1],n为stones大小,因为每次跳跃最多只能增加一步,所以逐个石子跳跃到达最后一个石子最多也只能跳跃n + 1步