【1654. 到家的最少跳跃次数】

来源:力扣(LeetCode)

描述:

有一只跳蚤的家在数轴上的位置 x 处。请你帮助它从位置 0 出发,到达它的家。

跳蚤跳跃的规则如下:

  • 它可以 往前 跳恰好 a 个位置(即往右跳)。
  • 它可以 往后 跳恰好 b 个位置(即往左跳)。
  • 它不能 连续 往后跳 2 次。
  • 它不能跳到任何 forbidden 数组中的位置。

跳蚤可以往前跳 超过 它的家的位置,但是它 不能跳到负整数 的位置。

给你一个整数数组 forbidden ,其中 forbidden[i] 是跳蚤不能跳到的位置,同时给你整数 abx ,请你返回跳蚤到家的最少跳跃次数。如果没有恰好到达 x 的可行方案,请你返回 -1

示例 1:

输入:forbidden = [14,4,18,1,15], a = 3, b = 15, x = 9
输出:3
解释:往前跳 3 次(0 -> 3 -> 6 -> 9),跳蚤就到家了。

示例 2:

输入:forbidden = [8,3,16,6,12,20], a = 15, b = 13, x = 11
输出:-1

示例 3:

输入:forbidden = [1,6,2,14,5,17,4], a = 16, b = 9, x = 7
输出:2
解释:往前跳一次(0 -> 16),然后往回跳一次(16 -> 7),跳蚤就到家了。

提示:

  • 1 <= forbidden.length <= 1000
  • 1 <= a, b, forbidden[i] <= 2000
  • 0 <= x <= 2000
  • forbidden 中所有位置互不相同。
  • 位置 x 不在 forbidden 中。

方法:广度优先搜索

思路

求最短路径一般需要用广度优先搜索,但是此题中的图是个无限图,如果不限制搜索的范围,无法处理无解的情况。因此,解决此题的关键是找出搜索的范围,其中下限已经由题目给出,不能跳到负整数的位置,我们还需要找出搜索的上限,下面分情况讨论:

  1. a = b。此时为了次数最少,跳蚤没有必要向后跳,只需要一直往前跳。当它超过 x 却没有遇到 x,表示它再也跳不到 x 了,此时的上限可以设置为 x。
  2. a > b。题目规定,跳蚤不能连续往后跳 2 次,因此这只跳蚤运动轨迹中,任意连续的两次跳跃,总的行程一定是在前进的,前进了 a−ba-ba−b 的距离。即使它某一步是在后退,这一步的前一步和后一步(如果有的话)一定是在前进。此时跳蚤运动的上限为 x + b,在这个上限的情况下,跳蚤往回跳一步可以到达 x。在大于这个上限的情况下,即使跳蚤马上往回跳一步,所处的位置也大于 x,而且跳蚤接下来前进的次数必然会大于等于后退的次数,再也无法到达 x。因此在这种情况下,上限为 x + b。
  3. a < b。在这种情况下,上限为 max⁡(max⁡(forbidden) + a + b, x)。接下来证明这一点。为了方便,记 max⁡(forbidden) = f。首先,需要将数轴上大于等于 0 的位置分为三个区域:
    • [0, f],禁止区。所有 forbidden 中的位置都位于这个区域。
    • (f, max⁡(f + a + b, x)],安全区,它的右边界是 a < b 情况下我们想要证明的广度优先搜索的上限。
    • (max⁡(f + a + b, x), +∞) ,界外区。

这三个区域合起来组成了数轴上大于等于 0 的所有部分,注意 x 可能位于禁止区或者安全区,但不会是 forbidden 数组中的元素。假设某个步数最少的路径中,点 C 是第一个进入界外区(前进进入)的点,而点 H 是第一个离开界外区(后退离开)的点。因为 x 只可能位于禁止区或者安全区,因此如果这条路径存在点 C,那么必然存在点 H。如下图,横坐标为步数,纵坐标为与原点的距离。箭头朝右上表示前进,箭头朝右下表示后退。
1

接下来,我们通过交换线段 BC 和线段 GH ,并保持其他线段的的方向不变,来使得点 C 不再位于界外区。如下图,线段 BC’ 变为后退而线段 G’H 变为前进。

2

我们从以下几个方面论证这种交换的可行性:

  • 交换前,点 C,D,…,F,G 全都位于界外区,与原点的距离大于 f + a + b 。通过交换,这些点与原点的距离缩小了 a + b ,仍然大于 f。因此,这些点不会落到 forbidden 中。
  • 交换后不会增加这个路径的步数,也不会影响点 H 之后的点的位置。
  • 交换不会造成两次倒退。交换后,前进的线段 BC 变为后退的线段 BC’ ,但是 BC’ 的前一段 AB 一定是前进的。可以利用反证法证明,如果 AB 是后退的,那么点 A 就会在界外区,因为 a < b ,这样的话点 C 就不会是第一个界外区的点,因此 AB 一定是前进的。BC’ 的后一段 C’D’ 一定也是前进的。这里需要分为两种情况:
    • CD 原本就是前进的,那么 C’D’ 会保持原来前进的方向。通过交换,我们不会造成两次倒退。
    • CD 原本是后退的,那么点 D 就是我们前面讨论的第一个离开界外区的点 H,因为 a < b。这样一来,我们其实是交换了前进的 BC 和后退的 CD,得到了后退的 BC’ 和前进的 C’D,仍然不会造成两次倒退。

通过这样的交换,我们使得一个有效路径第一个进入界外区的点,不再位于界外区。新的路径,第一个进入界外区的点,可能位于点 C 和点 H 之间,也可能位于点 HHH 之后,也可能不存在这样的点。总之,我们可以不停地寻找第一个进入界外区的点,然后经过上述的交换,使得最终的路径的所有点都位于禁止区和安全区。这样,我们就证明出,如果某个输入有解,那么至少有一条最短路径,它的所有点都处于上限 max⁡(f + a + b, x) 之内。因此在这种情况下,上限为 max⁡(max⁡(forbidden) + a + b, x)。

综合以上三种情况,广度优先搜索的上限是 max⁡(max⁡(forbidden) + a, x) + b 。

在进行广度优先搜索时,除了需要注意到上下限,不能达到 forbidden 数组中的坐标,还需要注意到达每个坐标时,都会有前进到达还是后退到达两种状态。如果是前进到达时,下一步可以选择前进或者后退;如果是后退到达时,下一步只能选择前进。因此广度优先搜索的每个元素,需要保存三个信息,坐标,方向和步数。在代码中,我们用 1 表示前进, −1 表示后退,用哈希集合 visited 来记录已经达到过的位置和方向状态。在搜索的过程中,如果坐标第一次为 x,则返回当前步数。当队列为空时,表示 x 不可到达,返回 −1。

代码:

class Solution {
public:int minimumJumps(vector<int>& forbidden, int a, int b, int x) {queue<tuple<int, int, int>> q;unordered_set<int> visited;q.emplace(0, 1, 0);visited.emplace(0);int lower = 0, upper = max(*max_element(forbidden.begin(), forbidden.end()) + a, x) + b;unordered_set<int> forbiddenSet(forbidden.begin(), forbidden.end());while (!q.empty()) {auto [position, direction, step] = q.front();q.pop();if (position == x) {return step;}int nextPosition = position + a;int nextDirection = 1;if (lower <= nextPosition && nextPosition <= upper && !visited.count(nextPosition * nextDirection) && !forbiddenSet.count(nextPosition)) {visited.emplace(nextPosition * nextDirection);q.emplace(nextPosition, nextDirection, step + 1);}if (direction == 1) {nextPosition = position - b;nextDirection = -1;if (lower <= nextPosition && nextPosition <= upper && !visited.count(nextPosition * nextDirection) && !forbiddenSet.count(nextPosition)) {visited.emplace(nextPosition * nextDirection);q.emplace(nextPosition, nextDirection, step + 1);}}}return -1;}
};

时间 32ms 击败 44.76%使用 C++ 的用户
内存 18.11MB 击败 43.81%使用 C++ 的用户
复杂度分析

  • 时间复杂度:O(max⁡(max⁡(forbidden)+a,x)+b)。表达式为广度优先搜索的位置上限,每个位置最多会被计算两次。
  • 空间复杂度:O(max⁡(max⁡(forbidden)+a,x)+b)。表达式为广度优先搜索的位置上限,是队列和哈希集合的空间复杂度。
    author:力扣官方题解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/64082.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git——Windows平台创建gitee私有仓库详解

目录 1. 安装git 2. gitbash配置 2.1 设置 2.2 生成key 2.3 项目管理 2.3.1 本地新建 2.3.2 clone远程仓库的工程到本地改文件 1. 安装git 默认安装。 2. gitbash配置 2.1 设置 打开gitbash&#xff0c;设置用户名和邮箱&#xff1a; git config --global user.name …

Web安全——穷举爆破上篇(仅供学习)

Web安全 一、概述二、常见的服务1、burpsuite 穷举后台密码2、burpsuite 对 webshell 穷举破解密码3、有 token 防御的网站后台穷举破解密码3.1 burpsuite 设置宏获取 token 对网站后台密码破解3.2 编写脚本获取token 对网站后台密码破解 4、针对有验证码后台的穷举方法4.1 coo…

stm32之IIC协议

主要通过两个层面来讲&#xff1a;物理层、协议层。 IIC是一个同步半双工串行总线协议。 一、物理层&#xff08;通信模型&#xff09; 1、最早是飞利浦公司开发的这个协议&#xff0c;最早应用到其产品上去。 2、两线制&#xff08;两根信号线&#xff09; 其中SCL为时钟…

双网卡/内外网同时使用2023.09.01

1.双网卡 电脑需要两个网卡&#xff1a;两个网口或者是一个有线网卡加一个无线网卡。 查看网关&#xff1a;如下网口接入网线后&#xff0c;电脑连接WIFI&#xff0c;电脑会显示存在两个网卡正在使用&#xff08;电脑存在两个IP地址&#xff09; 查看本地的路由设置 route p…

服务器数据恢复- RAID5出现故障后恢复数据和操作系统的案例

服务器数据恢复环境&#xff1a; 某品牌服务器中有4块SAS硬盘组建了一组RAID5阵列&#xff0c;另外1块磁盘作为热备盘使用。上层操作系统为redhat linux&#xff0c;部署了一个数据库是oracle的OA。 服务器故障&初检&#xff1a; RAID5中一块磁盘离线后热备盘未自动激活re…

设计模式-适配器

文章目录 一、简介二、适配器模式基础1. 适配器模式定义与分类2. 适配器模式的作用与优势3.UML图 三、适配器模式实现方式1. 类适配器模式2. 对象适配器模式3.类适配器模式和对象适配器模式对比 四、适配器模式应用场景1. 继承与接口的适配2. 跨平台适配 五、适配器模式与其他设…

大数据课程K16——Spark的梯度下降法

文章作者邮箱&#xff1a;yugongshiyesina.cn 地址&#xff1a;广东惠州 ▲ 本章节目的 ⚪ 了解Spark的梯度下降法&#xff1b; ⚪ 了解Spark的梯度下降法家族&#xff08;BGD&#xff0c;SGD&#xff0c;MBGD&#xff09;&#xff1b; ⚪ 掌握Spark的MLlib实现…

Vue生命周期(详细)

生命周期 图&#xff1a; 可以理解vue生命周期就是指vue实例从创建到销毁的过程&#xff0c;在vue中分为8个阶段&#xff1a;创建前/后&#xff0c;载入前/后&#xff0c;更新前/后&#xff0c;销毁前/后。 一、创建&#xff08;实例&#xff09; 1、beforeCreate&#xff1a…

C语言每日一练-------Day(9)

本专栏为c语言练习专栏&#xff0c;适合刚刚学完c语言的初学者。本专栏每天会不定时更新&#xff0c;通过每天练习&#xff0c;进一步对c语言的重难点知识进行更深入的学习。 今日练习题关键字&#xff1a;字符个数统计 多数元素 投票法 &#x1f493;博主csdn个人主页&#xf…

基于Stable Diffusion的AIGC服饰穿搭实践

本文主要介绍了基于Stable Diffusion技术的虚拟穿搭试衣的研究探索工作。文章展示了使用LoRA、ControlNet、Inpainting、SAM等工具的方法和处理流程&#xff0c;并陈述了部分目前的实践结果。通过阅读这篇文章&#xff0c;读者可以了解到如何运用Stable Diffusion进行实际操作&…

图:有向无环图(DAG)

1.有向无环图的定义 有向无环图:若一个有向图中不存在环&#xff0c;则称为有向无环图。 简称DAG图(Directed Acyclic Graph) 顶点中不可能出现重复的操作数。 2.有向无环图的应用 1.描述算数表达式 用有向无环图描述算术表达式。 解题步骤&#xff1a; 把各个操作数不重…

【前端入门案例1】HTML + CSS

案例一 <!DOCTYPE html> <html lang"en-US"><head><meta charset"utf-8"><meta name"viewport" content"widthdevice-width"><title>My test page</title> </head><body><…

word6 图文混排

目录 7-1 段落缩进排版7-2 搞定多级列表难题 7-1 段落缩进排版 段落对齐 缩进问题 悬挂缩进&#xff1a;缩进首行以外的段落 段落对齐&#xff1a; 7-2 搞定多级列表难题

【Python从入门到进阶】34、selenium基本概念及安装流程

接上篇《33、使用bs4获取星巴克产品信息》 上一篇我们介绍了如何使用bs4来解析星巴克网站&#xff0c;获取其产品信息。本篇我们来了解selenium技术的基础。 一、什么是selenium&#xff1f; Selenium是一种用于自动化Web浏览器操作的开源工具。它提供了一组API&#xff08;应…

Spring Cloud Foundry上使用通配符模式匹配进行的安全绕过漏洞 CVE-2023-20873

文章目录 0.前言1.参考文档2.基础介绍描述如果满足以下任一条件&#xff0c;应用程序就不会有太大风险&#xff1a;受影响的Spring产品和版本 3.解决方案3.1. 升级版本3.2. 替代方案 0.前言 背景&#xff1a;公司项目扫描到 Spring Cloud Foundry上使用通配符模式匹配进行的安全…

Qt应用开发(基础篇)——进度对话框 QProgressDialog

一、前言 QProgressDialog类继承于QDialog&#xff0c;是Qt设计用来反馈进度的对话框。 对话框QDialog QProgressDialog提供了一个进度条&#xff0c;表示当前程序的某操作的执行进度&#xff0c;让用户知道操作依旧在激活状态&#xff0c;配合按钮&#xff0c;用户就可以随时终…

15.CSS发光按钮的悬停特效

效果 源码 <!DOCTYPE html> <html> <head><title>CSS Modern Button</title><link rel="stylesheet" type="text/css" href="style.css"> </head> <body><a href="#" style=&quo…

Revit SDK:AutoParameter 添加参数

前言 这个例子介绍如果往族文件里添加参数。 内容 Revit 的参数&#xff0c;参考官方文档&#xff1a; 这个例子的关键接口&#xff1a; // 通过 FamilyManager 添加参数 FamilyParameter AddParameter(string parameterName, BuiltInParameterGroup parameterGroup, Categ…

C++ 学习之 构造函数 和 析构函数

前言 总的来说&#xff0c;构造函数负责对象的初始化&#xff0c;而析构函数负责对象的清理和资源释放。它们是C面向对象编程中非常重要的概念&#xff0c;用于管理对象的生命周期&#xff0c;确保对象在创建和销毁时都能够正确地进行初始化和清理。 正文 看代码 class perso…

【Java 基础篇】Java 方法使用详解:让你轻松掌握方法的奥秘

如果你正在学习Java编程&#xff0c;方法是一个不可或缺的重要概念。方法允许你将代码组织成可重用的块&#xff0c;提高了代码的可维护性和可读性。在本篇博客中&#xff0c;我们将深入探讨Java方法的使用&#xff0c;从基础概念开始&#xff0c;逐步介绍如何定义、调用、传递…