最小跳跃次数

标签: 广度优先搜索 线段树 数组 动态规划

难度: Hard

为了给刷题的同学一些奖励,力扣团队引入了一个弹簧游戏机。游戏机由 N 个特殊弹簧排成一排,编号为 0N-1。初始有一个小球在编号 0 的弹簧处。若小球在编号为 i 的弹簧处,通过按动弹簧,可以选择把小球向右弹射 jump[i] 的距离,或者向左弹射到任意左侧弹簧的位置。也就是说,在编号为 i 弹簧处按动弹簧,小球可以弹向 0i-1 中任意弹簧或者 i+jump[i] 的弹簧(若 i+jump[i]>=N ,则表示小球弹出了机器)。小球位于编号 0 处的弹簧时不能再向左弹。

为了获得奖励,你需要将小球弹出机器。请求出最少需要按动多少次弹簧,可以将小球从编号 0 弹簧弹出整个机器,即向右越过编号 N-1 的弹簧。

示例 1:

输入:jump = [2, 5, 1, 1, 1, 1]

输出:3

解释:小 Z 最少需要按动 3 次弹簧,小球依次到达的顺序为 0 -> 2 -> 1 -> 6,最终小球弹出了机器。

限制:

  • 1 <= jump.length <= 10^6
  • 1 <= jump[i] <= 10000

Submission

运行时间: 244 ms

内存: 79.4 MB

class Solution:
    def minJump(self, jump: List[int]) -> int:
        left_max_vis_idx = 0
        visited_status = set()
        queue = [0]
        n = len(jump)
        ans  = 0
        if n == 1:
            return 0
        while queue:
            tmp = queue
            queue = []
            ans += 1
            for item in tmp:
                next_item = item + jump[item]
                if next_item >= n:
                    return ans
                if next_item not in visited_status and next_item > left_max_vis_idx:
                    queue.append(next_item)
                    visited_status.add(next_item)
                if item > left_max_vis_idx:
                    for j in range(left_max_vis_idx + 1, item):
                        queue.append(j)
                    left_max_vis_idx = item
        return ans
                

Explain

The solution uses a breadth-first search (BFS) strategy, where each position on the springboard array 'jump' is treated as a node in a graph, and the possible jumps from each node define the edges. Starting from the first spring (index 0), the algorithm explores all possible jumps (both forward to 'i + jump[i]' and backward to any index less than 'i'). A queue is used to keep track of the next indices to visit. The 'visited_status' set ensures each index is only added once to the queue to prevent infinite loops. The BFS approach guarantees that the first time the algorithm reaches beyond the last index (N), the minimum number of moves has been found.

时间复杂度: O(n)

空间复杂度: O(n)

class Solution:
    def minJump(self, jump: List[int]) -> int:
        left_max_vis_idx = 0 # Tracks the furthest left index visited
        visited_status = set() # Keeps track of which indices have been visited
        queue = [0] # BFS queue starting from the first index
        n = len(jump) # Length of the jump array
        ans = 0 # Counter for the minimum number of jumps
        if n == 1:
            return 0 # If there's only one element, no jumps are needed
        while queue:
            tmp = queue
            queue = []
            ans += 1
            for item in tmp:
                next_item = item + jump[item] # Calculate next index using jump
                if next_item >= n:
                    return ans # Return answer if next index is out of bounds
                if next_item not in visited_status and next_item > left_max_vis_idx:
                    queue.append(next_item) # Enqueue valid, unvisited index
                    visited_status.add(next_item)
                if item > left_max_vis_idx:
                    for j in range(left_max_vis_idx + 1, item):
                        if j not in visited_status: # Enqueue all unvisited indices between last max visited index and current
                            queue.append(j)
                            visited_status.add(j)
                    left_max_vis_idx = item # Update the furthest left visited index
        return ans # Return the number of jumps needed if loop exits normally

Explore

在此问题中,使用宽度优先搜索(BFS)是因为我们需要找到从起点到终点的最短跳跃次数。BFS从起点开始,逐层探索所有可能的跳跃路径,确保一旦到达终点时,所用的跳跃次数是最少的。相比之下,深度优先搜索(DFS)可能会深入探索某一路径直到终点,但这不一定是最短跳跃路径。BFS通过逐层探索保证了一旦找到解决方案,那么它就是需要的最小跳跃次数。

`visited_status`集合存储了所有已经访问过的索引。在BFS过程中,每次访问一个新的索引前会检查该索引是否已在`visited_status`中。如果已存在,则不会再次将其加入待访问队列。这个机制有效防止了算法重复访问同一索引,避免了无限循环并减少了不必要的计算,保证了算法的效率和正确性。

变量`left_max_vis_idx`用于追踪在进行正向跳跃时,已经考虑过的最左边的索引位置。在BFS中,除了正向跳跃外,还需要考虑向所有未访问的更小索引的反向跳跃。`left_max_vis_idx`确保算法在向后遍历并将这些索引加入队列时不会重复访问已经处理过的索引。这种处理方式是对传统BFS的一个扩展,特别适用于跳跃问题,因为它需要同时处理向前和向后的跳跃条件,而且向后的跳跃需确保不遗漏任何可能的路径。