【LeetCode】跳跃游戏Ⅰ~Ⅵ(我真的跳晕了@_@)

【LeetCode】跳跃游戏Ⅰ~Ⅵ 😵😵😵


跳跃游戏★

LeetCode55. 跳跃游戏

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

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

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

示例

---输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 13 步到达最后一个位置
---输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置

解题思路

方法一:从左向右

使用right保存能够到达的最右位置,每访问一个位置,若当前位置小于等于最右位置,说明能够跳到当前位置,然后更新最右位置即可。

class Solution {
    public boolean canJump(int[] nums) {
        int n = nums.length, right = 0;
        for(int i = 0; i < n; i++) {
            if(i <= right && nums[i] + i > right) { 
                right = nums[i] + i;
            }
        }
        return right >= n - 1;
    }
}

方法二:从右向左(逆向思维)

使用left保存能够跳到终点的最左位置,逆向遍历,若当前位置能够跳到left,则更新left为当前下标,最后判断能否到达起点即可

class Solution {
    public boolean canJump(int[] nums) {
        if(nums == null || nums.length < 2) {
            return true;
        }
        int left = nums.length - 1;
        for(int i = nums.length - 2; i >= 0; i--) {
            if(i + nums[i] >= left) left = i;
        }
        return left == 0;
    }
}

跳跃游戏Ⅱ★★★

LeetCode45. 跳跃游戏 II

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

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

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

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

示例

输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2,从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置

解题思路

方法一

若当前位置为i,则它能够跳到的位置区间为[0, nums[i]],假如当前跳数为j,则 i + j 为跳到的位置,此位置可能之前已经跳到过,更新最小跳跃次数即可,状态方程为
d p [ i + j ] = m i n ( d p [ i + j ] , d p [ i ] + 1 ) dp[i + j] = min(dp[i+j], dp[i] + 1) dp[i+j]=min(dp[i+j],dp[i]+1)
这个思路是很容易想到的,但有一个测试用例会超时

class Solution {
    public int jump(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1000000);
        dp[0] = 0;
        for(int i = 0; i < n - 1; i++) {
            for(int j = 1; j <= nums[i]; j++) {
                if(i + j < n) {
                    dp[i + j] = Math.min(dp[i + j], dp[i] + 1);
                }
            }
        }
        return dp[n - 1];
    }
}

方法二

按题目要求一定能够从起点跳到终点,贪心取每一跳达到的最远位置即可。

reach是当前跳跃的右界限,nextreach是下一次跳跃的右界限,且nextreach是一直动态更新的

class Solution {
    public int jump(int[] nums) {
        int steps = 0;
        int reach = 0, nextreach = 0;
        for(int i = 0; i < nums.length - 1; i++) {
            nextreach = Math.max(nextreach, i + nums[i]);
            if(reach == i) {
                reach = nextreach;
                steps++;
            }
        }
        return steps;
    }
}

为了方便理解,对于示例nums = [2,3,1,1,4]的代码轨迹如下

nums = [2,3,1,1,4]
------------------------------------------
i = 0  reach = 2  nextreach = 2  steps = 1
i = 1  reach = 2  nextreach = 4  steps = 1
i = 2  reach = 4  nextreach = 4  steps = 2
i = 3  reach = 4  nextreach = 4  steps = 2

跳跃游戏Ⅲ★★

LeetCode1306. 跳跃游戏 III

题目】这里有一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处。当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]

请你判断自己是否能够跳到对应元素值为 0 的 任一 下标处。

注意,不管是什么情况下,你都无法跳到数组之外。

示例

---输入:arr = [4,2,3,0,3,1,2], start = 5
输出:true
解释:
到达值为 0 的下标 3 有以下可能方案: 
下标 5 -> 下标 4 -> 下标 1 -> 下标 3 
下标 5 -> 下标 6 -> 下标 4 -> 下标 1 -> 下标 3 
---输入:arr = [3,0,2,1,2], start = 2
输出:false
解释:无法到达值为 0 的下标 1

解题思路

方法一:深度优先搜索

class Solution {
    public boolean canReach(int[] arr, int start) {
        return dfs(arr, start);
    }

    private boolean dfs(int[] arr, int start) {
        if(start < 0 || start >= arr.length || arr[start] == -1) {
            return false;
        }
        if(arr[start] == 0) return true;
        int l = start + arr[start];
        int r = start - arr[start];
        arr[start] = -1;
        return dfs(arr, l) || dfs(arr, r);
    }
}

方法二:广度优先搜索

class Solution {
    public boolean canReach(int[] arr, int start) {
        boolean[] spot = new boolean[arr.length];
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(start);
        spot[start] = true;
        while(!queue.isEmpty()) {
            int cur = queue.poll();
            if(arr[cur] == 0) return true;
            int left = cur - arr[cur], right = cur + arr[cur];
            if(left >= 0 && !spot[left]) {
                queue.offer(left);
                spot[left] = true;
            }
            if(right < arr.length && !spot[right]) {
                queue.offer(right);
                spot[right] = true;
            }
        }
        return false;
    }
}

跳跃游戏 Ⅳ★★★

LeetCode1345. 跳跃游戏 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
解释:一开始就在最后一个元素处,所以你不需要跳跃。

解题思路

需要注意的是对于测试用例如 [1, 1, 1, 1, 2 , 2]可简化为[1, 1, 2, 2],同一段连续相同值只需首尾两个元素即可。

方法一:BFS + 动态规划

运行时长100ms,超越7%

class Solution {
    public int minJumps(int[] arr) {
        int n = arr.length;
        Map<Integer, List<Integer>> map = new HashMap<>();
        for(int i = 0; i < n; i++) {
            //同一段值只需头和尾, 如 1, 1, 1, 1, 3, 3 简化为 1, 1, 3, 3
            if(i > 0 && arr[i] == arr[i - 1] && i + 1 < n && arr[i] == arr[i + 1]) {
                continue;
            } 
            if(!map.containsKey(arr[i])) {
                map.put(arr[i], new ArrayList<Integer>());
            }
            map.get(arr[i]).add(i);
        }

        int[] dp = new int[n];
        Arrays.fill(dp, Integer.MAX_VALUE);
        Deque<Integer> queue = new ArrayDeque<>();
        dp[0] = 0;
        queue.offer(0);
        
        while(!queue.isEmpty()) {
            int i = queue.poll();
            //向左跳
            if(i - 1 >= 0 && dp[i - 1] > dp[i] + 1) {
                queue.offer(i - 1);
                dp[i - 1] = dp[i] + 1;
            }
            //向右跳
            if(i + 1 < n && dp[i + 1] > dp[i] + 1) {
                queue.offer(i + 1);
                dp[i + 1] = dp[i] + 1;
            }
            //同值跳跃
            for(int j = 0; j < map.get(arr[i]).size(); j++) {
                int k = map.get(arr[i]).get(j);
                if(i != k && dp[i] + 1 < dp[k]) {
                    queue.offer(k);
                    dp[k] = dp[i] + 1;
                }
            }
        }

        return dp[n - 1];
    }
}

方法二:优化BFS

运行时长52ms,超越84%

class Solution {
    public int minJumps(int[] arr) {
        int n = arr.length;
        Map<Integer, List<Integer>> map = new HashMap<>();
        for(int i = 0; i < n; i++) {
            //同一段值只需头和尾, 如 1, 1, 1, 1, 3, 3 简化为 1, 1, 3, 3
            if(i > 0 && arr[i] == arr[i - 1] && i + 1 < n && arr[i] == arr[i + 1]) {
                continue;
            } 
            if(!map.containsKey(arr[i])) {
                map.put(arr[i], new ArrayList<Integer>());
            }
            map.get(arr[i]).add(i);
        }

        int steps = 0;
        boolean[] visted = new boolean[n];
        Deque<Integer> queue = new LinkedList<>();
        visted[0] = true;
        queue.offer(0);
        
        while(!queue.isEmpty()) {
           int size = queue.size();
           for(int i = 0; i < size; i++) {
               int j = queue.poll();
               if(j == n - 1) return steps;
               //向左跳
               if(j - 1 >= 0 && !visted[j - 1]) {
                   queue.offer(j - 1);
                   visted[j - 1] = true;
               }
               //向右跳
               if(j + 1 < n && !visted[j + 1]) {
                   queue.offer(j + 1);
                   visted[j + 1] = true;
               }
               //同值跳跃
               for(int k = 0; k < map.get(arr[j]).size(); k++) {
                   int next = map.get(arr[j]).get(k);
                   if(next != j && !visted[next]) {
                       queue.offer(next);
                       visted[next] = true;
                   }
               }
           }
           steps++;
        }

        return steps;
    }
}

跳跃游戏 Ⅴ★★★

LeetCode1340. 跳跃游戏 V

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

  • i + x ,其中 i + x < arr.length0 < x <= d
  • i - x ,其中 i - x >= 00 < x <= d

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

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

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

示例

在这里插入图片描述

----输入: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 在下标 46 之间且 13 > 9 。
类似的,你不能从下标 3 处跳到下标 2 或者下标 1 处。

----输入:arr = [3,3,3,3,3], d = 3
输出:1
解释:你可以从任意下标处开始且你永远无法跳到任何其他坐标。

解题思路】记忆化搜索

class Solution {
    private int[] dp;

    public int maxJumps(int[] arr, int d) {
        int n = arr.length;
        dp = new int[n];
        int res = 0;
        for(int i = 0; i < n; i++) {
            res = Math.max(res, dfs(arr, d, i, n));
        }
        return res;
    }

    private int dfs(int[] arr, int d, int i, int n) {
        if(dp[i] != 0) return dp[i];
        dp[i] = 1;
        //向左搜索
        for(int j = 1; j <= d && i - j >= 0 && arr[i] > arr[i - j]; j++) {
            dp[i] = Math.max(dp[i], dfs(arr, d, i - j, n) + 1);
        }
        //向右搜索
        for(int j = 1; j <= d && i + j < n && arr[i] > arr[i + j]; j++) {
            dp[i] = Math.max(dp[i], dfs(arr, d, i + j, n) + 1);
        }
        return dp[i];
    }
}

跳跃游戏Ⅵ★★

LeetCode 1696. 跳跃游戏 VI

题目】给你一个下标从 0 开始的整数数组 nums 和一个整数 k

一开始你在下标 0 处。每一步,你最多可以往前跳 k 步,但你不能跳出数组的边界。也就是说,你可以从下标 i 跳到 [i + 1, min(n - 1, i + k)] 包含 两个端点的任意位置。

你的目标是到达数组最后一个位置(下标为 n - 1 ),你的 得分 为经过的所有数字之和。

请你返回你能得到的 最大得分

示例

输入:nums = [1,-1,-2,4,-7,3], k = 2
输出:7
解释:你可以选择子序列 [1,-1,4,3] (上面加粗的数字),和为 7

解题思路

方法一:常规dp

时间复杂度为O(n²),会超时

class Solution {
    public int maxResult(int[] nums, int k) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, Integer.MIN_VALUE);
        dp[0] = nums[0];
        for(int i = 1; i < n; i++) {
            for(int j = Math.max(0, i - k); j < i; j++) {
                dp[i] = Math.max(dp[i], dp[j]);
            }
            dp[i] += nums[i];
        }
        return dp[n - 1];
    }
}

方法二:优先队列

class Solution {
    public int maxResult(int[] nums, int k) {
        int n = nums.length;
        Queue<int[]> queue = new PriorityQueue<int[]>((o1, o2) -> {
            return o2[1] - o1[1];
        });
        queue.offer(new int[]{0, nums[0]});
        int res = nums[0];
        for(int i = 1; i < n; i++) {
            while(queue.peek()[0] + k < i) {
                queue.poll();
            }
            res = queue.peek()[1] + nums[i];
            queue.offer(new int[]{i, res});
        }
        return res;
    }
}

方法三:单调队列

class Solution {
    public int maxResult(int[] nums, int k) {
        int n = nums.length;
        LinkedList<int[]> queue = new LinkedList<int[]>();
        queue.offer(new int[]{0, nums[0]});
        int res = nums[0];
        for(int i = 1; i < n; i++) {
            while(queue.peekFirst()[0] + k < i) {
                queue.pollFirst();
            }
            res = queue.peekFirst()[1] + nums[i];
            while(!queue.isEmpty() && queue.peekLast()[1] < res) {
                queue.pollLast();
            }
            queue.offer(new int[]{i, res});
        }
        return res;
    }
}

最少侧跳次数★★

1824. 最少侧跳次数

题目】给你一个长度为n3跑道道路 ,它总共包含n + 1 个 点 ,编号为 0n 。一只青蛙从0号点第二条跑道 **出发 **,它想要跳到点 n 处。然而道路上可能有一些障碍。

给你一个长度为n + 1的数组obstacles,其中 obstacles[i]取值范围从 0 到 3)表示在点i处的 obstacles[i]跑道上有一个障碍。如果obstacles[i] == 0,那么点i处没有障碍。任何一个点的三条跑道中 最多有一个 障碍。

  • 比方说,如果 obstacles[2] == 1,那么说明在点 2处跑道 1有障碍。
    这只青蛙从点i跳到点i + 1 且跑道不变的前提是点i + 1的同一跑道上没有障碍。为了躲避障碍,这只青蛙也可以在 **同一个 **点处 **侧跳 **到 **另外一条 **跑道(这两条跑道可以不相邻),但前提是跳过去的跑道该点处没有障碍。

  • 比方说,这只青蛙可以从点3 处的跑道 3跳到点 3处的跑道1

这只青蛙从点0处跑道 2 出发,并想到达点 n 处的 任一跑道 ,请你返回 最少侧跳次数

注意:点0处和点 n处的任一跑道都不会有障碍。

提示

  • obstacles.length == n + 1
  • 1 <= n <= 5 * 105
  • 0 <= obstacles[i] <= 3
  • obstacles[0] == obstacles[n] == 0

示例

示例1

在这里插入图片描述

输入:obstacles = [0,1,2,3,0]
输出:2 
解释:最优方案如上图箭头所示。总共有 2 次侧跳(红色箭头)。
注意,这只青蛙只有当侧跳时才可以跳过障碍(如上图点 2 处所示)。

示例2
在这里插入图片描述

输入:obstacles = [0,2,1,0,3,0]
输出:2
解释:最优方案如上图所示。总共有 2 次侧跳。

解题思路

动态规划

class Solution {
    public int minSideJumps(int[] obstacles) {
        int n = obstacles.length;
        int[][] dp = new int[n + 1][4];
        int maxVal = (int)(5 * 1e5 + 100);
        for (int[] p : dp) {
            Arrays.fill(p, maxVal);
        }
        dp[1][1] = 1;
        dp[1][2] = 0;
        dp[1][3] = 1;
        for (int i = 2; i <= n; i++) {
            if (obstacles[i - 1] != 1) dp[i][1] = dp[i - 1][1];
            if (obstacles[i - 1] != 2) dp[i][2] = dp[i - 1][2];
            if (obstacles[i - 1] != 3) dp[i][3] = dp[i - 1][3];
            if (obstacles[i - 1] != 1) {
                dp[i][1] = Math.min(dp[i][1], Math.min(dp[i][2], dp[i][3]) + 1);
            }
            if (obstacles[i - 1] != 2) {
                dp[i][2] = Math.min(dp[i][2], Math.min(dp[i][1], dp[i][3]) + 1);
            }
            if (obstacles[i - 1] != 3) {
                dp[i][3] = Math.min(dp[i][3], Math.min(dp[i][1], dp[i][2]) + 1);
            }
        }
        return Math.min(dp[n][1], Math.min(dp[n][2], dp[n][3]));
    }
}

青蛙过河★★★

403. 青蛙过河

题目

一只青蛙想要过河。 假定河流被等分为若干个单元格,并且在每一个单元格内都有可能放有一块石子(也有可能没有)。 青蛙可以跳上石子,但是不可以跳入水中。

给你石子的位置列表 stones(用单元格序号 升序 表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一块石子上)。开始时, 青蛙默认已站在第一块石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格 1 跳至单元格 2 )。如果青蛙上一步跳跃了 k个单位,那么它接下来的跳跃距离只能选择为k - 1kk + 1个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。

提示:

  • 2 <= stones.length <= 2000
  • 0 <= stones[i] <= 231 - 1
  • stones[0] == 0

示例

输入:stones = [0,1,3,5,6,8,12,17]
输出:true
解释:青蛙可以成功过河,按照如下方案跳跃:跳 1 个单位到第 2 块石子, 然后跳 2 个单位到第 3 块石子, 接着 跳 2 个单位到第 4 块石子, 然后跳 3 个单位到第 6 块石子,4 个单位到第 7 块石子, 最后,跳 5 个单位到第 8 个石子(即最后一块石子)。
---------------------------------------------------------------------------------------
输入:stones = [0,1,2,3,4,8,9,11]
输出:false
解释:这是因为第 5 和第 6 个石子之间的间距太大,没有可选的方案供青蛙跳跃过去。

解题思路

动态规划,状态转移方程如下
d p [ 0 ] [ 0 ] = t r u e dp[0][0]=true dp[0][0]=true

d p [ i ] [ k ] = ( d p [ j ] [ k − 1 ] ) ∣ ( d p [ j ] [ k ] ) ∣ ( d p [ j ] [ k + 1 ] ) dp[i][k] = (dp[j][k - 1])|(dp[j][k])|(dp[j][k + 1]) dp[i][k]=(dp[j][k1])(dp[j][k])(dp[j][k+1])

class Solution {
    public boolean canCross(int[] stones) {
        int n = stones.length;
        if (n < 2) {
            return true;
        }
        if (stones[1] != 1) {
            return false;
        }
        
        boolean[][] dp = new boolean[n][n + 1];
        dp[0][0] = true;
        
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                int k = stones[i] - stones[j];
                if (k <= j + 1) {
                    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;
    }
}

🎈

白鳯于2021/5/14更新

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白鳯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值