leetcode
leetcode 2351 ~ 2400
达到末尾下标所需的最大跳跃次数

达到末尾下标所需的最大跳跃次数

难度:

标签:

题目描述

You are given a 0-indexed array nums of n integers and an integer target.

You are initially positioned at index 0. In one step, you can jump from index i to any index j such that:

  • 0 <= i < j < n
  • -target <= nums[j] - nums[i] <= target

Return the maximum number of jumps you can make to reach index n - 1.

If there is no way to reach index n - 1, return -1.

 

Example 1:

Input: nums = [1,3,6,4,1,2], target = 2
Output: 3
Explanation: To go from index 0 to index n - 1 with the maximum number of jumps, you can perform the following jumping sequence:
- Jump from index 0 to index 1. 
- Jump from index 1 to index 3.
- Jump from index 3 to index 5.
It can be proven that there is no other jumping sequence that goes from 0 to n - 1 with more than 3 jumps. Hence, the answer is 3. 

Example 2:

Input: nums = [1,3,6,4,1,2], target = 3
Output: 5
Explanation: To go from index 0 to index n - 1 with the maximum number of jumps, you can perform the following jumping sequence:
- Jump from index 0 to index 1.
- Jump from index 1 to index 2.
- Jump from index 2 to index 3.
- Jump from index 3 to index 4.
- Jump from index 4 to index 5.
It can be proven that there is no other jumping sequence that goes from 0 to n - 1 with more than 5 jumps. Hence, the answer is 5. 

Example 3:

Input: nums = [1,3,6,4,1,2], target = 0
Output: -1
Explanation: It can be proven that there is no jumping sequence that goes from 0 to n - 1. Hence, the answer is -1. 

 

Constraints:

  • 2 <= nums.length == n <= 1000
  • -109 <= nums[i] <= 109
  • 0 <= target <= 2 * 109

代码结果

运行时间: 263 ms, 内存: 16.9 MB


/*
 * 思路:
 * 使用动态规划和Java Stream API来简化部分操作。
 * 和上面的思路类似,维护一个dp数组来存储到达每个位置的最大跳跃次数。
 * 使用IntStream遍历来实现动态规划的跳跃过程。
 */

import java.util.*;
import java.util.stream.*;

public class Solution {
    public int maxJump(int[] nums, int target) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, -1);
        dp[0] = 0;

        IntStream.range(0, n).forEach(i -> {
            if (dp[i] != -1) {
                IntStream.range(i + 1, n).forEach(j -> {
                    if (nums[j] - nums[i] >= -target && nums[j] - nums[i] <= target) {
                        dp[j] = Math.max(dp[j], dp[i] + 1);
                    }
                });
            }
        });

        return dp[n - 1];
    }
}

解释

方法:

该题解采用了线段树(ZKW线段树的非递归版本)来求解问题。首先,为了应对题目中允许的跳跃范围 [-target, target],题解将数组中的每个元素 x 扩展到三个可能的跳跃值 x-target, x, x+target,并去重排序。这种扩展允许在 O(log n) 时间内查询和更新任何区间的最大值。通过映射每个扩展值到其索引,可以用线段树高效查询和更新最大的跳跃次数。遍历 nums 数组,对于每个元素 x,查询在 [x-target, x+target] 范围内的最大跳跃次数,并在线段树中更新当前 x 的位置。最后,检查是否能到达 n-1 索引处,并返回相应的跳跃次数。如果不能到达,则返回 -1。

时间复杂度:

O(n log n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在处理跳跃范围时,选择将每个元素扩展为 x-target, x, x+target 三个可能的值?这种设计对算法的效率和结果有什么影响?
在处理跳跃范围时,选择将每个元素扩展为 x-target, x, x+target 是为了覆盖所有可能的跳跃目的地。这样做可以确保算法能够考虑到从当前位置可能到达的所有位置。这种设计使得算法能够在更新和查询操作中直接访问到任意跳跃的目的地,极大地提高了效率。同时,这种方法需要更多的空间来存储扩展后的值,并且需要额外的计算来处理这些值,但这是为了确保算法的正确性和全面性。
🦆
题解中提到的线段树(ZKW线段树的非递归版本)的具体实现细节是什么?特别是如何在非递归的情况下实现区间的查询和更新?
ZKW线段树的非递归版本是一种自底向上的更新方法,不需要使用递归调用。在这种实现中,数组的元素首先被放置在线段树的底部,对应于叶子节点。对于更新操作,首先更新叶子节点,然后向上更新其祖先节点直到根节点。对于查询操作,从底层开始,逐层向上,根据查询区间的边界动态决定查询路径,合并左右子区间的结果。这种方式避免了递归造成的额外开销,提高了效率。
🦆
题解中的线段树初始化时为什么将每个元素默认值设为 -inf,这个选择对算法的执行有什么特别的意义或影响?
将每个元素的默认值设为 -inf 是为了正确处理还未经过更新的线段树节点。因为本题需要计算最大跳跃次数,未更新的节点应该不对最终结果产生影响(即被视为负无穷大,在求最大值时被忽略)。这样在进行区间最大值查询时,只有那些已经被设置或更新过的节点才会对结果有贡献,确保算法的正确性和初始状态的一致性。
🦆
在算法的最后阶段,如果最终的跳跃次数 q 为负值,返回 -1 表示无法到达最后一个索引。这种情况是如何出现的?具体是在什么情况下会出现 q<0?
如果最终的跳跃次数 q 为负值,意味着在尝试跳跃到最后一个索引的过程中,不存在有效的跳跃路径可以到达该索引。这通常发生在没有任何可用跳跃能够到达最后一个位置时,例如,如果前面所有的跳跃都不能覆盖到达最后位置的范围。在这种情况下,线段树查询返回的是初始化的 -inf 值,表明从起始点到终点无有效路径,因此算法返回 -1 来表示无法完成任务。

相关问题