1306. 跳跃游戏 III
这里有一个非负整数数组 arr
,你最开始位于该数组的起始下标 start
处。当你位于下标 i
处时,你可以跳到 i + arr[i]
或者 i - arr[i]
。
请你判断自己是否能够跳到对应元素值为 0 的 任一 下标处。
注意,不管是什么情况下,你都无法跳到数组之外。
示例 1:
输入:arr = [4,2,3,0,3,1,2], start = 5
输出:true
解释:
到达值为 0 的下标 3 有以下可能方案:
下标 5 -> 下标 4 -> 下标 1 -> 下标 3
下标 5 -> 下标 6 -> 下标 4 -> 下标 1 -> 下标 3
示例 2:
输入:arr = [4,2,3,0,3,1,2], start = 0
输出:true
解释:
到达值为 0 的下标 3 有以下可能方案:
下标 0 -> 下标 4 -> 下标 1 -> 下标 3
示例 3:
输入:arr = [3,0,2,1,2], start = 2
输出:false
解释:无法到达值为 0 的下标 1 处。
提示:
1 <= arr.length <= 5 * 10^4
0 <= arr[i] < arr.length
0 <= start < arr.length
解题思路
方法一: 递归(深度优先搜索)
递归判断往前跳arr[start]步和往后跳arr[start]步是否能到达目的地。这种办法当前在LeetCode发生了栈溢出。
public boolean canReach(int[] arr, int start) {
if (arr[start] == 0) {
return true;
}
boolean canReachDes = false;
int posPost, posPre;
// 尝试往前跳, 看是否能跳成功
if ((posPost = start + arr[start]) < arr.length) {
canReachDes = canReach(arr, posPost);
}
if (canReachDes) {
return true;
}
// 尝试往后跳, 看是否能跳成功
if ((posPre = start - arr[start]) >= 0) {
return canReach(arr, posPre);
}
return false;
}
上述代码是一段有可能产生死循环的代码。例如在数组[1, 1, 2, 0]中, 如果下标从0处开始, 会一直沿着下标 0 -> 1 -> 2 -> 0 - > 1 -> 2....的路径死循环下去。
解决死循环的问题很简单, 只需要把访问过的路径记住。如果下次又回到了之前访问过的节点, 那么就知道是发生死循环了, 直接返回false
private static int UNKNOWN_CODE = 0;
private static int REACH_CODE = 1;
private static int UNREACH_CODE = 2;
private int[] canReach;
private boolean[] visit;
public boolean canReach(int[] arr, int start) {
this.canReach = new int[arr.length];
this.visit = new boolean[arr.length];
return canReachToZero(arr, start);
}
public boolean canReachToZero(int[] arr, int start) {
if (arr[start] == 0) {
return true;
}
// 记忆化数组
if (canReach[start] != UNKNOWN_CODE) {
return canReach[start] == 1;
}
// 看当前节点是否之前访问过, 如果访问过则发生了死循环, 直接返回false
if (!visit[start]) {
visit[start] = true;
} else {
return false;
}
boolean canReachDes = false;
int posPost, posPre;
// 往后找, 看是否能到达
if ((posPost = start + arr[start]) < arr.length) {
canReachDes = canReachToZero(arr, posPost);
}
if (canReachDes) {
return true;
}
// 往前找, 看是否能到达
if ((posPre = start - arr[start]) >= 0) {
canReachDes = canReachToZero(arr, posPre);
}
// 记录并且返回当前的结果
canReach[start] = canReachDes ? REACH_CODE : UNREACH_CODE;
return canReachDes;
}
方法二: 非递归(广度优先搜索)
public boolean canReach2(int[] arr, int start) {
if (arr[start] == 0) {
return true;
}
int n = arr.length;
boolean[] used = new boolean[n];
Deque<Integer> q = new ArrayDeque<>();;
q.push(start);
used[start] = true;
while (!q.isEmpty()) {
int u = q.peekFirst();
q.pop();
if (u + arr[u] < n && !used[u + arr[u]]) {
// 往右计算, 没有访问过
if (arr[u + arr[u]] == 0) {
return true;
}
// 就把右边的这个节点加入队列当中
q.push(u + arr[u]);
// 设置右边这个节点已经防卫过
used[u + arr[u]] = true;
}
if (u - arr[u] >= 0 && !used[u - arr[u]]) {
// 同时对左边的节点做同样的操作
if (arr[u - arr[u]] == 0) {
return true;
}
q.push(u - arr[u]);
used[u - arr[u]] = true;
}
}
return false;
}