BFS解力扣1654最短跳跃次数

对于 LeetCode 1654 "到家的最少跳跃次数"这道题,核心是使用广度优先搜索 (BFS) 来寻找从起点 0 到目标位置 x 的最短路径。解题的关键在于对搜索空间进行合理的限制 ,并正确处理"不能连续向后跳"的约束。

问题解构与约束分析

约束条件 说明
跳跃规则 1. 向前跳 a 步。 2. 向后跳 b 步。
特殊限制 1. 不能连续两次向后跳 。 2. 不能跳到 forbidden 数组中的位置。 3. 不能跳到负数位置。
目标 从位置 `
0出发,到达位置x` 的最少跳跃次数。

关键难点与解决方案:

  1. 无限搜索空间 :由于可以向前和向后跳,理论上搜索可以无限进行。必须设定一个合理的上界。
  2. "不能连续向后跳" :需要在状态中记录上一次跳跃的方向,以决定本次可选的跳跃方式。
  3. 状态定义与去重 :一个位置可能被以不同的"上次跳跃方向"访问到,需要记录 (位置, 上次是否向后跳) 这样一个组合状态来避免重复搜索。

方案推演与核心思路

  1. BFS 状态设计:

    • 每个 BFS 节点需要包含:(当前位置 cur, 上次是否向后跳 is_back, 当前步数 step)。
    • 使用一个集合 visited 记录已访问的 (cur, is_back) 状态,避免重复搜索。
  2. 搜索上界确定:

    • 参考中的分析,一个常用的、安全的右边界是 max(max(forbidden) + a, x) + b。更保守且普遍采用的上界是 6000(根据题目数据范围,这是一个经验值,足以覆盖所有可达状态)。本文将采用 6000 作为上界。
  3. BFS 过程:

    • 从初始状态 (0, False, 0) 开始。
    • 对于每个状态 (cur, is_back, step):
      • 如果 cur == x,返回 step。
      • 尝试向前跳 到 next_cur = cur + a。若位置合法、非禁止、且状态未访问,则入队,并标记 (next_cur, False) 为已访问。
      • 尝试向后跳 到 next_cur = cur - b。前提是 is_back == False(上次没向后跳)、位置合法、非禁止、且状态未访问,则入队,并标记 (next_cur, True) 为已访问。

代码实现 (Python)

python 复制代码
from collections import deque

class Solution:
    def minimumJumps(self, forbidden: List[int], a: int, b: int, x: int) -> int:
        """
        :type forbidden: List[int]
        :type a: int
        :type b: int
        :type x: int
        :rtype: int
        """
        if x == 0:
            return 0

        # 将禁止列表转换为集合,便于O(1)查找
        forbid_set = set(forbidden)
        # BFS搜索上界,6000是一个经验值,足以覆盖题目数据范围
        MAX_BOUND = 6000
        # 访问状态记录,使用(位置, 是否由向后跳抵达)作为键
        visited = set()
        # 队列元素: (当前位置, 是否由向后跳抵达, 当前步数)
        queue = deque()
        queue.append((0, False, 0))
        visited.add((0, False))

        while queue:
            cur_pos, is_back, steps = queue.popleft()

            # 尝试向前跳
            next_pos = cur_pos + a
            # 检查位置是否合法:未出界、非禁止点、状态未访问
            if 0 <= next_pos <= MAX_BOUND and next_pos not in forbid_set and (next_pos, False) not in visited:
                if next_pos == x:
                    return steps + 1
                visited.add((next_pos, False))
                queue.append((next_pos, False, steps + 1))

            # 尝试向后跳 (前提:上一次不是向后跳)
            if not is_back:
                next_pos = cur_pos - b
                # 检查位置是否合法:非负、非禁止点、状态未访问
                if next_pos >= 0 and next_pos not in forbid_set and (next_pos, True) not in visited:
                    if next_pos == x:
                        return steps + 1
                    visited.add((next_pos, True))
                    queue.append((next_pos, True, steps + 1))

        # BFS队列清空仍未找到目标,说明不可达
        return -1

关键点与示例分析

为什么上界是 6000?

这是一个基于题目数据范围的工程经验值。题目中 a, b, x 均不超过 2000,forbidden 长度不超过 1000。通过分析,最坏情况下需要探索的范围不会超过 max(x, max(forbidden)) + a + b 再乘以一个较小的系数,6000 是一个足够大且安全的边界,可以避免无限循环,同时不会引起不必要的性能开销。

状态 (位置, 是否由向后跳抵达) 的重要性

考虑以下场景:从位置 p 通过向前跳抵达位置 q,和通过向后跳抵达位置 q,这两种状态是不等价 的。因为后者意味着下一次移动不能向后跳,而前者可以。因此,必须区分这两种情况,否则会丢失可行解或导致错误。

示例运行 :

以题目示例 forbidden = [14,4,18,1,15], a = 3, b = 15, x = 9 为例:

  1. 初始状态 (0, False, 0)。
  2. 从0向前跳3步到3,状态 (3, False, 1)。
  3. 从3向前跳3步到6,状态 (6, False, 2)。
  4. 从6向前跳3步到9,发现 9 == x,返回步数 3。
    程序输出结果为 3,与示例一致。

关于一个"神奇BUG"的说明

在中,作者提到了一个在特定代码结构下 return 或 break 可能无法立即退出的情况。这通常与循环和条件判断的嵌套逻辑有关,并非语言本身的BUG。上述提供的代码结构清晰,在找到目标时立即返回 steps + 1,可以正确退出。


参考来源

相关推荐
Omics Pro18 分钟前
反式感知虚拟细胞!超越线性序列
数据库·人工智能·算法·机器学习·自然语言处理
-dzk-22 分钟前
【动态规划】LC 152.乘积最大子数组
算法·动态规划
积流成海24 分钟前
一致性哈希原理与实现:分布式系统负载均衡
算法·负载均衡·哈希算法·哈希函数·盘子工具站
Ada's10 小时前
【计算机基础系列】003:Python数据结构
开发语言·数据结构·python
Nil20811 小时前
leetcode 118杨辉三角
算法·leetcode·职场和发展
wanderist.13 小时前
线性筛法详解:从筛质数到欧拉函数、Möbius 函数与约数函数
java·数据结构·算法
All for pursuit.13 小时前
【设计-1】208.实现Trie (前缀树)
数据结构·c++·算法·leetcode
weixin_3077791313 小时前
C++代码实现MATLAB中的crossval函数功能
开发语言·c++·算法·matlab
青少儿编程课堂14 小时前
背包问题(0/1 背包与完全背包)解题精讲——动态规划入门
c++·python·算法·bfs·信息学竞赛
光影少年14 小时前
为什么 JavaScript 中 0.1 + 0.2 !== 0.3,如何让其相等?
前端·javascript·算法