天天看点

动态规划-贪心-BFS-跳跃游戏

2020-05-04 17:21:37

55. 跳跃游戏

问题描述:

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个位置。

示例 1:

输入: [2,3,1,1,4]

输出: true

解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。

示例 2:

输入: [3,2,1,0,4]

输出: false

解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

问题求解:

维护rightMost即可。

时间复杂度:O(n)

public boolean canJump(int[] nums) {
        int rightMost = 0;
        for (int i = 0; i < nums.length; i++) {
            if (i > rightMost) return false;
            rightMost = Math.max(rightMost, i + nums[i]);
            if (rightMost >= nums.length - 1) return true;
        }
        return true;
    }
      

 

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 -> 下标 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

BFS经典问题。

public boolean canReach(int[] arr, int start) {
        int n = arr.length;
        int[] used = new int[n];
        Queue<Integer> q = new LinkedList<>();
        q.add(start);
        used[start] = 1;
        while (!q.isEmpty()) {
            int curr = q.poll();
            int l = curr - arr[curr];
            int r = curr + arr[curr];
            if (l >= 0 && arr[l] == 0) return true;
            if (r < n && arr[r] == 0) return true;
            if (l >= 0 && used[l] == 0) {
                q.add(l);
                used[l] = 1;
            }
            if (r < n && used[r] == 0) {
                q.add(r);
                used[r] = 1;
            }
        }
        return false;
    }
      

45. 跳跃游戏 II

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

示例:

输出: 2

解释: 跳到最后一个位置的最小跳跃数是 2。

  从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

说明:

假设你总是可以到达数组的最后一个位置。

解法一:DP

时间复杂度:O(n ^ 2)

public int jump(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        dp[n - 1] = 0;
        for (int i = n - 2; i >= 0; i--) {
            dp[i] = Integer.MAX_VALUE / 2;
            if (nums[i] + i >= n - 1) dp[i] = 1;
            else {
                int step = dp[nums[i] + i];
                for (int j = nums[i] + i; j > i; j--) step = Math.min(step, dp[j]);
                dp[i] = step + 1;
            }
        }
        return dp[0];
    }      

解法二:BFS 

数组上的BFS。

public int jump(int[] nums) {
        int n = nums.length;
        int layer = 0;
        int nlayer = 0;
        int step = 0;
        for (int i = 0; i < n - 1; i++) {
            if (i <= layer) {
                nlayer = Math.max(nlayer, i + nums[i]);
                if (nlayer >= n - 1) return step + 1;
            }
            else {
                layer = nlayer;
                nlayer = 0;
                step++;
                i--;
            }
        }
        return 0;
    }
      

  

1345. 跳跃游戏 IV

给你一个整数数组 arr ,你一开始在数组的第一个元素处(下标为 0)。

每一步,你可以从下标 i 跳到下标:

i + 1 满足:i + 1 < arr.length

i - 1 满足:i - 1 >= 0

j 满足:arr[i] == arr[j] 且 i != j

请你返回到达数组最后一个元素的下标处所需的 最少操作次数 。

注意:任何时候你都不能跳到数组外面。

输入:arr = [100,-23,-23,404,100,23,23,23,3,404]

输出:3

解释:那你需要跳跃 3 次,下标依次为 0 --> 4 --> 3 --> 9 。下标 9 为数组的最后一个元素的下标。

输入:arr = [7]

输出:0

解释:一开始就在最后一个元素处,所以你不需要跳跃。

输入:arr = [7,6,9,6,9,6,9,7]

输出:1

解释:你可以直接从下标 0 处跳到下标 7 处,也就是数组的最后一个元素处。

示例 4:

输入:arr = [6,1,9]

输出:2

示例 5:

输入:arr = [11,22,7,7,7,7,7,7,7,22,13]

-10^8 <= arr[i] <= 10^8

带传送门的BFS,需要注意的要删去传送门。

public int minJumps(int[] arr) {
        if (arr.length <= 1) return 0;
        int n = arr.length;
        Map<Integer, List<Integer>> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            if (!map.containsKey(arr[i])) map.put(arr[i], new ArrayList<>());
            map.get(arr[i]).add(i);
        }
        Queue<Integer> q = new LinkedList<>();
        int[] used = new int[n];
        Set<Integer> set = new HashSet<>();
        q.add(0);
        used[0] = 1;
        int step = 0;
        while (!q.isEmpty()) {
            int size = q.size();
            for (int k = 0; k < size; k++) {
                int curr = q.poll();
                if (curr - 1 >= 0 && used[curr - 1] == 0) {
                    q.add(curr - 1);
                    used[curr - 1] = 1;
                }
                if (curr + 1 < n && used[curr + 1] == 0) {
                    if (curr + 1 == n - 1) return step + 1;
                    q.add(curr + 1);
                    used[curr + 1] = 1;
                }
                if (!set.contains(arr[curr])) {
                    set.add(arr[curr]);
                    for (int next : map.get(arr[curr])) {
                        if (next == n - 1) return step + 1;
                        if (used[next] == 1) continue;
                        q.add(next);
                        used[next] = 1;
                    }
                }
            }
            step += 1;
        }
        return -1;
    }
      

1340. 跳跃游戏 V

给你一个整数数组 arr 和一个整数 d 。每一步你可以从下标 i 跳到:

i + x ,其中 i + x < arr.length 且 0 < x <= d 。

i - x ,其中 i - x >= 0 且 0 < x <= d 。

除此以外,你从下标 i 跳到下标 j 需要满足:arr[i] > arr[j] 且 arr[i] > arr[k] ,其中下标 k 是所有 i 到 j 之间的数字(更正式的,min(i, j) < k < max(i, j))。

你可以选择数组的任意下标开始跳跃。请你返回你 最多 可以访问多少个下标。

请注意,任何时刻你都不能跳到数组的外面。

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/jump-game-v

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

输入:arr = [6,4,14,6,8,13,9,7,10,6,12], d = 2

输出:4

解释:你可以从下标 10 出发,然后如上图依次经过 10 --> 8 --> 6 --> 7 。

注意,如果你从下标 6 开始,你只能跳到下标 7 处。你不能跳到下标 5 处因为 13 > 9 。你也不能跳到下标 4 处,因为下标 5 在下标 4 和 6 之间且 13 > 9 。

类似的,你不能从下标 3 处跳到下标 2 或者下标 1 处。

输入:arr = [3,3,3,3,3], d = 3

解释:你可以从任意下标处开始且你永远无法跳到任何其他坐标。

输入:arr = [7,6,5,4,3,2,1], d = 1

输出:7

解释:从下标 0 处开始,你可以按照数值从大到小,访问所有的下标。

输入:arr = [7,1,7,1,7,1], d = 2

输入:arr = [66], d = 1

1 <= arr.length <= 1000

1 <= arr[i] <= 10^5

1 <= d <= arr.length

解法一:递归DP

最容易想到的解法就是Top-down DP。

时间复杂度:O(nd)

public int maxJumps(int[] arr, int d) {
        int n = arr.length;
        int[] dp = new int[n];
        for (int i = 0; i < n; i++) {
            if (dp[i] == 0) dp[i] = dfs(arr, d, i, dp);
        }
        int res = dp[0];
        for (int i = 0; i < n; i++) res = Math.max(res, dp[i]);
        return res;
    }
    
    private int dfs(int[] arr, int d, int idx, int[] dp) {
        if (dp[idx] != 0) return dp[idx];
        dp[idx] = 1;
        for (int i = idx - 1; i >= Math.max(0, idx - d); i--) {
            if (arr[i] >= arr[idx]) break;
            dp[idx] = Math.max(dp[idx], dfs(arr, d, i, dp) + 1);
        }
        for (int i = idx + 1; i <= Math.min(arr.length - 1, idx + d); i++) {
            if (arr[i] >= arr[idx]) break;
            dp[idx] = Math.max(dp[idx], dfs(arr, d, i, dp) + 1);
        }
        return dp[idx];
    }
      

解法二:递推DP

递推DP有两个需要明确,一个是递推公式,一个是初始化,这里的递推公式是写明了的,重要的就是初始化。

在本题中每个元素的step是需要从更小的数得到的,因此需要排序原数组,从小到大去更新dp。

public int maxJumps(int[] arr, int d) {
        int res = 1;
        int n = arr.length;
        int[] dp = new int[n];
        PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> Integer.compare(o1[1], o2[1]));
        for (int i = 0; i < n; i++) pq.add(new int[]{i, arr[i]});
        while (!pq.isEmpty()) {
            int[] curr = pq.poll();
            int idx = curr[0];
            int val = curr[1];
            dp[idx] = 1;
            for (int i = idx - 1; i >= Math.max(0, idx - d); i--) {
                if (arr[i] >= val) break;
                dp[idx] = Math.max(dp[idx], dp[i] + 1);
            }
            for (int i = idx + 1; i <= Math.min(n - 1, idx + d); i++) {
                if (arr[i] >= val) break;
                dp[idx] = Math.max(dp[idx], dp[i] + 1);
            }
            res = Math.max(res, dp[idx]);
        }
        return res;
    }