403. 青蛙过河
思路一:DFS 暴搜
class Solution {
HashMap<Integer,Integer> map = new HashMap<>();
// step: 上一步走了多少步
// idx: 当前石头块编号
public boolean dfs(int[] stones, int step,int idx){
if(idx == (stones.length-1)){
// System.out.print("OK!");
return true;
}
for(int i=-1;i<=1;i++){
if((step+i)==0) continue; // 如果这次选择原地不动就跳过
int crtStep = step+i;
int crtDist = stones[idx]+crtStep;
if(map.containsKey(crtDist) && dfs(stones,crtStep,map.get(crtDist)))
return true;
}
return false;
}
public boolean canCross(int[] stones) {
if((stones[1]-stones[0])!=1) return false;
// 构建一个距离 -> stones index的HashMap
for(int i=0;i<stones.length;i++)
map.put(stones[i],i);
return dfs(stones,1,1);
}
}
思路二:记忆化
在考虑加入「记忆化」时,我们只需要将 DFS 方法签名中的【可变】参数作为维度,DFS 方法中的返回值作为存储值即可。
class Solution {
HashMap<Integer,Integer> map = new HashMap<>();
Map<String, Boolean> cache = new HashMap<>(); // 用于记忆画存储
// step: 上一步走了多少步
// idx: 当前石头块编号
// 我们这里每步的String格式是"idx_step"
public boolean dfs(int[] stones, int step,int idx){
String tmp = idx+"_"+step;
// 如果之前遇到过
if(cache.containsKey(tmp)) return cache.get(tmp);
if(idx == (stones.length-1)){
// System.out.print("OK!");
return true;
}
for(int i=-1;i<=1;i++){
if((step+i)==0) continue; // 如果这次选择原地不动就跳过
int crtStep = step+i;
int crtDist = stones[idx]+crtStep;
if(map.containsKey(crtDist)){
boolean res = dfs(stones,crtStep,map.get(crtDist));
cache.put(tmp,res);
if(res) return true;
}
}
cache.put(tmp,false); // 因为这种情况下不管这一步走多少都不行
return false;
}
public boolean canCross(int[] stones) {
if((stones[1]-stones[0])!=1) return false;
// 构建一个距离 -> stones index的HashMap
for(int i=0;i<stones.length;i++)
map.put(stones[i],i);
return dfs(stones,1,1);
}
}
思路三:动态规划
因此可以设定为 f[][]作为动规数组:
- 第一维为可变参数 u,代表当前处于的石子列表的下标,范围为数组 stones 长度;
- 第二维为可变参数 k,代表上一步的的跳跃步长,前面也分析过了,最多不超过数组 stones 长度。
class Solution {
public boolean canCross(int[] ss) {
int n = ss.length;
// check first step
if (ss[1] != 1) return false;
boolean[][] f = new boolean[n + 1][n + 1];
f[1][1] = true;
for (int i = 2; i < n; i++) {
for (int j = 1; j < i; j++) {
int k = ss[i] - ss[j];
// 我们知道从位置 j 到位置 i 是需要步长为 k 的跳跃
// 而从位置 j 发起的跳跃最多不超过 j + 1
// 因为每次跳跃,下标至少增加 1,而步长最多增加 1
if (k <= j + 1) {
// 来i之前是在j跳了k步,则j之前一个节点到达j走的步数只可能是k-1,k,k+1。
f[i][k] = f[j][k - 1] || f[j][k] || f[j][k + 1];
}
}
}
for (int i = 1; i < n; i++) {
if (f[n - 1][i]) return true;
}
return false;
}
}