LeetCode 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)
)。
你可以选择数组的任意下标开始跳跃。请你返回你 最多 可以访问多少个下标。
请注意,任何时刻你都不能跳到数组的外面。
示例 1:
输入: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 处。
示例 2:
输入:arr = [3,3,3,3,3], d = 3
输出:1
解释:你可以从任意下标处开始且你永远无法跳到任何其他坐标。
示例 3:
输入:arr = [7,6,5,4,3,2,1], d = 1
输出:7
解释:从下标 0 处开始,你可以按照数值从大到小,访问所有的下标。
示例 4:
输入:arr = [7,1,7,1,7,1], d = 2
输出:2
示例 5:
输入:arr = [66], d = 1
输出:1
提示:
1 <= arr.length <= 1000
1 <= arr[i] <= 10^5
1 <= d <= arr.length
问题分析
这道题是跳跃游戏系列的第五题,有以下特点:
- 跳跃规则:
- 可以向左或向右跳跃,跳跃距离范围是 [1, d]
- 只能从高处往低处跳(arr[i] > arr[j])
- 跳跃路径上的所有点都必须比起点低(arr[i] > arr[k],对于所有 i 到 j 之间的 k)
- 目标:找出从任意起点出发,最多可以访问多少个下标。
- 关键点:
- 可以从任意下标开始
- 需要计算的是最大访问点数,而不是是否能到达某个特定目标
这个问题适合使用动态规划来解决,因为跳跃决策具有重叠子问题的性质。同时,由于跳跃的方向性(只能从高处跳到低处),我们可以按高度排序来确定解决问题的顺序。
解题思路
动态规划 + 记忆化搜索
我们可以定义 dp[i] 表示从下标 i 开始跳跃,最多可以访问的下标数量(包括起点自身)。
递推关系如下:
- 对于每个下标 i,我们尝试向左或向右跳跃距离 x(其中 1 <= x <= d)
- 如果可以跳到下标 j,那么 dp[i] = max(dp[i], 1 + dp[j])
由于跳跃方向是从高到低,我们需要确保先计算出较低位置的 dp 值,再计算较高位置的 dp 值。一种方法是使用记忆化搜索(也称为自顶向下的动态规划)。
算法步骤
- 创建一个 dp 数组,dp[i] 表示从下标 i 开始最多可以访问的下标数量
- 将所有 dp[i] 初始化为 1(至少可以访问自身)
- 使用记忆化搜索,对每个下标 i:
- 尝试向左或向右跳跃距离 x(1 <= x <= d)
- 检查跳跃条件:目标位置在数组范围内,且路径上所有点都比起点低
- 如果可以跳到下标 j,则 dp[i] = max(dp[i], 1 + dp[j])
- 返回所有 dp[i] 中的最大值
算法过程
以示例1为例:arr = [6,4,14,6,8,13,9,7,10,6,12], d = 2
让我们跟踪从下标10开始的DFS过程(这是最优起点):
- 初始化:
- dp[10] = 1(至少可以访问自身)
- 当前下标:10,对应值:12
- 尝试向左跳:
- 检查下标10-1=9:arr[10] > arr[9] (12 > 6) ✓
- 递归计算 dp[9]
- dp[9] = 1(至少可以访问自身)
- 由于没有可跳的地方,dp[9] = 1
- 检查下标10-2=8:arr[10] > arr[8] (12 > 10) ✓
- 递归计算 dp[8]
- dp[8] = 1
- 检查下标8-1=7:arr[8] > arr[7] (10 > 7) ✓
- 递归计算 dp[7]
- dp[7] = 1
- 检查下标7-1=6:arr[7] > arr[6] (7 < 9) ✗ 不能跳
- 检查下标7-2=5:超出范围(d=2)
- 所以 dp[7] = 1
- 更新 dp[8] = 1 + dp[7] = 2
- 检查下标8-2=6:arr[8] > arr[6] (10 > 9) ✓
- 已计算 dp[6] = 1(没有可跳的地方)
- 更新 dp[8] = max(2, 1+1) = 2
- 更新 dp[10] = max(1, 1+dp[8]) = 1+2 = 3
- 检查下标10-1=9:arr[10] > arr[9] (12 > 6) ✓
- 最终路径:
- 下标10 -> 下标8 -> 下标7 -> 可能的终点(无法继续跳跃)
- 或者 下标10 -> 下标8 -> 下标6 -> 可能的终点
- 最多访问4个下标(包括起点10)
事实上,完整的最优路径是:10 -> 8 -> 6 -> 7,总共访问4个下标。
详细代码实现
Java 实现
class Solution {private int[] dp;private int[] arr;private int d;public int maxJumps(int[] arr, int d) {int n = arr.length;this.arr = arr;this.d = d;this.dp = new int[n];// 初始化dp数组,每个位置至少可以访问自身Arrays.fill(dp, -1);int maxVisited = 0;for (int i = 0; i < n; i++) {maxVisited = Math.max(maxVisited, dfs(i));}return maxVisited;}private int dfs(int i) {// 如果已经计算过,直接返回if (dp[i] != -1) {return dp[i];}// 至少可以访问自身dp[i] = 1;// 尝试向右跳for (int j = i + 1; j <= Math.min(i + d, arr.length - 1); j++) {// 检查跳跃条件if (arr[i] <= arr[j]) {break;}// 检查路径上的所有点boolean canJump = true;for (int k = i + 1; k < j; k++) {if (arr[k] >= arr[i]) {canJump = false;break;}}if (canJump) {dp[i] = Math.max(dp[i], 1 + dfs(j));}}// 尝试向左跳for (int j = i - 1; j >= Math.max(i - d, 0); j--) {// 检查跳跃条件if (arr[i] <= arr[j]) {break;}// 检查路径上的所有点boolean canJump = true;for (int k = i - 1; k > j; k--) {if (arr[k] >= arr[i]) {canJump = false;break;}}if (canJump) {dp[i] = Math.max(dp[i], 1 + dfs(j));}}return dp[i];}
}
C# 实现
public class Solution {private int[] dp;private int[] arr;private int d;public int MaxJumps(int[] arr, int d) {int n = arr.Length;this.arr = arr;this.d = d;this.dp = new int[n];// 初始化dp数组for (int i = 0; i < n; i++) {dp[i] = -1;}int maxVisited = 0;for (int i = 0; i < n; i++) {maxVisited = Math.Max(maxVisited, Dfs(i));}return maxVisited;}private int Dfs(int i) {// 如果已经计算过,直接返回if (dp[i] != -1) {return dp[i];}// 至少可以访问自身dp[i] = 1;// 尝试向右跳for (int j = i + 1; j <= Math.Min(i + d, arr.Length - 1); j++) {// 检查跳跃条件if (arr[i] <= arr[j]) {break;}// 检查路径上的所有点bool canJump = true;for (int k = i + 1; k < j; k++) {if (arr[k] >= arr[i]) {canJump = false;break;}}if (canJump) {dp[i] = Math.Max(dp[i], 1 + Dfs(j));}}// 尝试向左跳for (int j = i - 1; j >= Math.Max(i - d, 0); j--) {// 检查跳跃条件if (arr[i] <= arr[j]) {break;}// 检查路径上的所有点bool canJump = true;for (int k = i - 1; k > j; k--) {if (arr[k] >= arr[i]) {canJump = false;break;}}if (canJump) {dp[i] = Math.Max(dp[i], 1 + Dfs(j));}}return dp[i];}
}
复杂度分析
- 时间复杂度:O(n²),其中n是数组的长度。在最坏情况下,对于每个位置,我们需要检查最多2d个可能的跳跃目标,总时间复杂度为O(n * d)。由于d最大可达n,所以最坏情况下时间复杂度是O(n²)。
- 空间复杂度:O(n),主要用于存储dp数组和递归调用栈。
优化:单调栈方法
上面的实现中,检查路径上的所有点是否都比起点低的时间复杂度是 O(d),我们可以使用单调栈来优化这一过程,降低时间复杂度。
Java 实现
class Solution {public int maxJumps(int[] arr, int d) {int n = arr.length;int[] dp = new int[n];Arrays.fill(dp, -1);// 将下标按照高度排序,从低到高处理Integer[] indices = new Integer[n];for (int i = 0; i < n; i++) {indices[i] = i;}Arrays.sort(indices, (a, b) -> arr[a] - arr[b]);int maxVisited = 0;for (int idx : indices) {maxVisited = Math.max(maxVisited, dfs(arr, d, idx, dp));}return maxVisited;}private int dfs(int[] arr, int d, int i, int[] dp) {if (dp[i] != -1) {return dp[i];}dp[i] = 1; // 至少可以访问自身// 向右跳for (int j = i + 1; j <= Math.min(i + d, arr.length - 1); j++) {if (arr[i] > arr[j]) {dp[i] = Math.max(dp[i], 1 + dfs(arr, d, j, dp));}// 如果遇到更高或相等的点,则无法继续向右if (arr[j] >= arr[i]) {break;}}// 向左跳for (int j = i - 1; j >= Math.max(i - d, 0); j--) {if (arr[i] > arr[j]) {dp[i] = Math.max(dp[i], 1 + dfs(arr, d, j, dp));}// 如果遇到更高或相等的点,则无法继续向左if (arr[j] >= arr[i]) {break;}}return dp[i];}
}
C#实现
public class Solution {public int MaxJumps(int[] arr, int d) {int n = arr.Length;int[] dp = new int[n];// 初始化dp数组,所有值设为-1表示未计算for (int i = 0; i < n; i++) {dp[i] = -1;}// 将下标按照高度排序,从低到高处理int[] indices = new int[n];for (int i = 0; i < n; i++) {indices[i] = i;}Array.Sort(indices, (a, b) => arr[a] - arr[b]);int maxVisited = 0;foreach (int idx in indices) {maxVisited = Math.Max(maxVisited, Dfs(arr, d, idx, dp));}return maxVisited;}private int Dfs(int[] arr, int d, int i, int[] dp) {// 如果已经计算过,直接返回if (dp[i] != -1) {return dp[i];}// 至少可以访问自身dp[i] = 1;// 向右跳for (int j = i + 1; j <= Math.Min(i + d, arr.Length - 1); j++) {if (arr[i] > arr[j]) {dp[i] = Math.Max(dp[i], 1 + Dfs(arr, d, j, dp));}// 如果遇到更高或相等的点,则无法继续向右if (arr[j] >= arr[i]) {break;}}// 向左跳for (int j = i - 1; j >= Math.Max(i - d, 0); j--) {if (arr[i] > arr[j]) {dp[i] = Math.Max(dp[i], 1 + Dfs(arr, d, j, dp));}// 如果遇到更高或相等的点,则无法继续向左if (arr[j] >= arr[i]) {break;}}return dp[i];}
}
复杂度分析
- 时间复杂度:O(n log n + n * d)
- 排序下标需要O(n log n)时间
- 对于每个位置,我们最多检查2d个可能的跳跃目标,总共n个位置,所以是O(n * d)
- 综合起来就是O(n log n + n * d)
- 空间复杂度:O(n)
- 主要用于存储dp数组、排序后的下标数组和递归调用栈
优化与技巧
- 按高度排序处理:先计算高度较低的点的dp值,再计算高度较高的点的dp值,可以减少重复计算。
- 提前终止:如果遇到高度大于等于当前点的位置,可以提前终止搜索,因为无法跳过这个位置。
- 记忆化搜索:使用dp数组存储已计算的结果,避免重复计算。
- 边界检查:确保跳跃不会超出数组范围。
- 利用问题特性:由于只能从高处跳到低处,整个跳跃路径形成了一个有向无环图(DAG),这使得动态规划可以正确解决此问题。
相关文章:

LeetCode 1340. 跳跃游戏 V(困难)
题目描述 给你一个整数数组 arr 和一个整数 d 。每一步你可以从下标 i 跳到: i x ,其中 i x < arr.length 且 0 < x < d 。i - x ,其中 i - x > 0 且 0 < x < d 。 除此以外,你从下标 i 跳到下标 j 需要满…...

x-cmd install | cargo-selector:优雅管理 Rust 项目二进制与示例,开发体验升级
目录 功能亮点安装优势特点适用场景总结 还在为 Rust 项目中众多的二进制文件和示例而烦恼吗?cargo-selector 让你告别繁琐的命令行,轻松选择并运行目标程序! 功能亮点 交互式选择: 在终端中以交互方式浏览你的二进制文件和示例&…...
数据库设计文档撰写攻略
数据库设计文档撰写攻略 一、数据库设计文档的核心价值二、数据库设计文档的核心框架与内容详解2.1 文档基础信息2.2 需求分析与设计原则2.2.1 业务需求概述2.2.2 设计原则 2.3 数据模型设计2.3.1 概念模型(ER 图)2.3.2 逻辑模型(表结构设计&…...
Python爬虫(10)Python数据存储实战:基于pymongo的MongoDB开发深度指南
目录 一、为什么需要文档型数据库?1.1 数据存储的范式变革1.2 pymongo的核心优势 二、pymongo核心操作全解析2.1 环境准备2.2 数据库连接与CRUD操作2.3 聚合管道实战2.4 分批次插入百万级数据(进阶)2.5 分批次插入百万级数据(进阶…...

大模型「瘦身」指南:从LLaMA到MobileBERT的轻量化部署实战
大模型「瘦身」指南:从LLaMA到MobileBERT的轻量化部署实战 系统化学习人工智能网站(收藏):https://www.captainbed.cn/flu 文章目录 大模型「瘦身」指南:从LLaMA到MobileBERT的轻量化部署实战摘要引言一、轻量化技术…...

从逻辑视角学习信息论:概念框架与实践指南
文章目录 一、信息论的逻辑基础与哲学内涵1.1 信息的逻辑本质:区分与差异1.2 逆范围原理与信息内容 二、信息论与逻辑学的概念交汇2.1 熵作为逻辑不确定性的度量2.2 互信息与逻辑依赖2.3 信道容量的逻辑极限 三、信息论的核心原理与逻辑基础3.1 最大熵原理的逻辑正当…...
springboot配置mysql druid连接池,以及连接池参数解释
文章目录 前置配置方式参数解释 前置 springboot 项目javamysqldruid 连接池 配置方式 在 springboot 的 application.yml 中配置基本方式 # Druid 配置(Spring Boot YAML 格式) spring:datasource:url: jdbc:mysql://localhost:3306/testdb?useSSL…...
Spring Boot集成Resilience4j实现微服务容错机制
在Spring Boot中集成Resilience4j实现微服务容错 引言 在微服务架构中,服务之间的调用不可避免,但由于网络延迟、服务不可用等问题,调用失败的情况时有发生。为了提高系统的稳定性和可用性,我们需要引入容错机制。Resilience4j是…...
(一) 本地hadoop虚拟机系统设置
1.配置固定IP地址(每一台都配置) 开启node1,修改主机名为node1,并修改固定IP为:192.168.88.131 # 修改主机名 hostnamectl set-hostname node1# 修改IP vim /etc/sysconfig/network-scripts/ifcfg-ens33 IPADDR"…...

TDengine 运维—容量规划
概述 若计划使用 TDengine 搭建一个时序数据平台,须提前对计算资源、存储资源和网络资源进行详细规划,以确保满足业务场景的需求。通常 TDengine 会运行多个进程,包括 taosd、taosadapter、taoskeeper、taos-explorer 和 taosx。 在这些进程…...
【MySQL成神之路】MySQL索引相关介绍
1 相关理论介绍 一、索引基础概念 二、索引类型 1. 按数据结构分类 2. 按功能分类 三、索引数据结构原理 B树索引特点: 哈希索引特点: 四、索引使用原则 1. 创建索引原则 2. 避免索引失效情况 五、索引优化策略 六、索引维护与管理 七、特殊…...

PPP 拨号失败:ATD*99***1# ... failed
从日志来看,主要有两类问题: 一、led_indicator_stop 报 invalid p_handle E (5750) led_indicator: …/led_indicator.c:461 (led_indicator_stop):invalid p_handle原因分析 led_indicator_stop() 的参数 p_handle (即之前 led_indicator…...
PostgreSQL跨数据库表字段值复制实战经验分
场景需求 在实际工作中,我们经常需要将一个PostgreSQL数据库中的表字段值复制到另一个数据库中。最近我在处理两个ERP系统数据库(A库和B库)之间的数据同步时,就遇到了这样的需求:需要将B库中sale_order表的合同信息&a…...

【计网】五六章习题测试
目录 1. (单选题, 3 分)某个网络所分配到的地址块为172.16.0.0/29,能接收目的地址为172.16.0.7的IP分组的最大主机数是( )。 2. (单选题, 3 分)若将某个“/19”的CIDR地址块划分为7个子块,则可能的最小子块中的可分配IP地址数量…...
汇川EasyPLC MODBUS-RTU通信配置和编程实现
累积流量计算(MODBUS RTU通信数据处理)数据处理相关内容。 累积流量计算(MODBUS RTU通信数据处理)_流量积算仪modbus rtu通讯-CSDN博客文章浏览阅读219次。1、常用通信数据处理MODBUS通信系列之数据处理_modbus模拟的数据变化后会在原来的基础上累加是为什么-CSDN博客MODBUS通…...

从 CANopen到 PROFINET:网关助力物流中心实现复杂的自动化升级
使用 CANopen PLC 扩展改造物流中心的传送带 倍讯科技profinet转CANopen网关BX-601-EIP将新的 PROFINET PLC 系统与旧的基于 CANopen 的传送带连接起来,简化了物流中心的自动化升级。 新建还是升级?这些问题通常出现在复杂的内部物流设施中,…...

基于Yolov8+PyQT的老人摔倒识别系统源码
概述 基于Yolov8PyQT的老人摔倒识别系统,该系统通过深度学习算法实时检测人体姿态,精准识别站立、摔倒中等3种状态,为家庭或养老机构提供及时预警功能。 主要内容 完整可运行代码 项目采用Yolov8目标检测框架结合PyQT5开发…...

wsl2 不能联网
wsl2 安装后用 wifi 共享是能联网,问题出在公司网络限制 wsl2 IP 访问网络,但是主机可以上网。 解决办法,在主机用 nginx 设置代理,可能需要开端口权限 server {listen 9000;server_name localhost;location /ubuntu/ {#…...
双击重复请求的方法
1、限制点击次数 2、vue中 可以自定义一个属性指令 preventReClick.js中定义: import Vue from vue Vue.directive(preventReClick, {inserted: (el, binding) > {el.addEventListener(click, () > {if (!el.disabled) {el.disabled truesetTimeout(() >…...

Java[IDEA]里的debug
目录 前言 Debug 使用Debug 总结 前言 这里我说一下就是 java IDEA 工具里的debug工具 里的一个小问题 就是 当我们使用debug去查看内部文档 查看不到 是为什么 Debug 所谓 debug 工具 他就是用来调试程序的 当我们写代码 报错 出错时 我们就可以使用这个工具 因此这个工具…...
一条SQL语句的旅程:解析、优化与执行全过程研究
1、引言 在现代信息系统中,数据库是核心组件之一。SQL(结构化查询语言)作为与数据库交互的主要方式,其执行效率直接影响到整个系统的性能表现。虽然开发者常常只需编写一行简单的 SQL,但数据库内部却经历了一个复杂而精密的过程来完成这条 SQL 的处理。 本文将以一个完整…...
动态规划经典三题_完全平方数
279. 完全平方数 给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而…...
LVGL(lv_textarea文本框控件)
文章目录 一、lv_textarea 是什么?二、基本用法1. 创建 lv_textarea 对象2. 设置提示文字(占位符)3. 设置最大长度4. 设置密码模式(显示为\*号)5. 获取和设置内容6. 配合虚拟键盘使用(常用于触摸屏…...
蓝桥杯国14 互质
问题描述 请计算在 [1,2023的2023次幂] 范围内有多少个整数与 2023 互质。由于结果可能很大,你只需要输出对 1097 取模之后的结果。 答案提交 这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个…...

DAO模式
1. 持久化 简单来说,就是把代码的处理结果转换成需要的格式进行储存。 2. JDBC的封装 3. DAO模式 4. Properties类与Properties配置文件 添加 读取 5. 使用实体类传递数据 6. 总结 附录: BaseDao指南 BaseDao指南-CSDN博客...

ECharts图表工厂,完整代码+思路逻辑
Echart工厂支持柱状图(bar)折线图(line)散点图(scatter)饼图(pie)雷达图(radar)极坐标柱状图(polarBar)和极坐标折线图(po…...
Logback 在 Spring Boot 中的详细配置
1. Logback 配置文件 Spring Boot 默认会加载 classpath 下的 logback-spring.xml(推荐)或 logback.xml 作为 Logback 的配置文件。 推荐使用 logback-spring.xml,因为 Spring Boot 提供了扩展支持(例如基于 Profile 的配置&am…...
写起来比较复杂的深搜题目
年轻的拉尔夫开玩笑地从一个小镇上偷走了一辆车,但他没想到的是那辆车属于警察局,并且车上装有用于发射车子移动路线的装置。 那个装置太旧了,以至于只能发射关于那辆车的移动路线的方向信息。 编写程序,通过使用一张小镇的地图…...
MySQL强化关键_016_存储引擎
目 录 一、概述 二、MySQL 支持的存储引擎 三、指定存储引擎 四、修改存储引擎 五、常用存储引擎及适用场景 一、概述 MySQL 存储引擎决定了数据在磁盘上的存储方式和访问方式;不同的存储引擎实现了不同的存储和检索算法;MySQL 常见的存储引擎&…...

CSS:margin的塌陷与合并问题
文章目录 一、margin塌陷问题二、margin合并问题 一、margin塌陷问题 二、margin合并问题...