Medium | LeetCode 1306. 跳跃游戏 III | 深度优先遍历(递归) | 广度优先遍历

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;
}
原文地址:https://www.cnblogs.com/chenrj97/p/14590946.html