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;
}