当前位置: 首页 > news >正文

LeetCode 1696. 跳跃游戏 VI(中等)

题目描述

给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。

一开始你在下标 0 处。每一步,你最多可以往前跳 k 步,但你不能跳出数组的边界。也就是说,你可以从下标 i 跳到 [i + 1, min(n - 1, i + k)] 包含 两个端点的任意位置。

你的目标是到达数组最后一个位置(下标为 n - 1 ),你的 得分 为经过的所有数字之和。

请你返回你能得到的 最大得分 。

示例 1:

输入:nums = [1,-1,-2,4,-7,3], k = 2
输出:7
解释:你可以选择子序列 [1,-1,4,3] (上面加粗的数字),和为 7 。

示例 2:

输入:nums = [10,-5,-2,4,0,3], k = 3
输出:17
解释:你可以选择子序列 [10,4,3] (上面加粗数字),和为 17 。

示例 3:

输入:nums = [1,-5,-20,4,-1,3,-6,-3], k = 2
输出:0

提示:

  •  1 <= nums.length, k <= 10^5
  • -10^4 <= nums[i] <= 10^4

问题分析

这道题是跳跃游戏系列的第六题,与前面的题目相比有以下特点:

  1. 跳跃规则:从位置 i 可以跳到 [i+1, min(n-1, i+k)] 范围内的任意位置
  2. 目标:到达数组最后一个位置,并使得经过的所有数字之和最大
  3. 约束:必须从下标 0 开始,必须到达下标 n-1

这是一个典型的动态规划问题,但如果使用朴素的动态规划,时间复杂度会是 O(nk),在给定的数据规模下可能会超时。因此,我们需要使用优化的方法。


解题思路

动态规划基本思路

定义 dp[i] 表示到达位置 i 的最大得分。

状态转移方程:

dp[i] = max(dp[j]) + nums[i],其中 max(0, i-k) <= j < i

初始状态:dp[0] = nums[0]

最终答案:dp[n-1]

优化方法

朴素的动态规划需要对每个位置 i 遍历前面 k 个位置来找最大值,时间复杂度为 O(nk)。我们可以使用以下方法优化:

  1. 优先队列(堆):维护一个最大堆,存储前面位置的 dp 值和对应的下标
  2. 单调队列:维护一个单调递减的双端队列,队首始终是当前窗口内的最大值

算法过程

以示例1为例:nums = [1,-1,-2,4,-7,3], k = 2

使用单调队列的执行过程:

  • 初始化:
    • dp[0] = 1
    • 队列:[(1, 0)]
  • i = 1:
    • 队首 (1, 0) 在范围内
    • dp[1] = 1 + (-1) = 0
    • 移除队尾小于等于0的元素:移除 (1, 0)
    • 队列:[(0, 1)]
  • i = 2:
    • 队首 (0, 1) 在范围内
    • dp[2] = 0 + (-2) = -2
    • 队列保持:[(0, 1), (-2, 2)]
  • i = 3:
    • 队首 (0, 1) 在范围内
    • dp[3] = 0 + 4 = 4
    • 移除队尾所有小于等于4的元素:移除 (0, 1) 和 (-2, 2)
    • 队列:[(4, 3)]
  • i = 4:
    • 队首 (4, 3) 在范围内
    • dp[4] = 4 + (-7) = -3
    • 队列:[(4, 3), (-3, 4)]
  • i = 5:
    • 队首 (4, 3) 在范围内
    • dp[5] = 4 + 3 = 7
    • 移除队尾所有小于等于7的元素:移除 (4, 3) 和 (-3, 4)
    • 队列:[(7, 5)]

最终答案:dp[5] = 7


详细代码实现

Java 实现 - 优先队列

import java.util.*;class Solution {public int maxResult(int[] nums, int k) {int n = nums.length;int[] dp = new int[n];// 优先队列,存储 [得分, 下标],按得分降序排列PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> b[0] - a[0]);// 初始状态dp[0] = nums[0];pq.offer(new int[]{dp[0], 0});for (int i = 1; i < n; i++) {// 移除超出跳跃范围的元素while (!pq.isEmpty() && pq.peek()[1] < i - k) {pq.poll();}// 计算当前位置的最大得分dp[i] = pq.peek()[0] + nums[i];// 将当前位置的得分加入优先队列pq.offer(new int[]{dp[i], i});}return dp[n - 1];}
}

C# 实现 - 优先队列

using System;
using System.Collections.Generic;public class Solution {public int MaxResult(int[] nums, int k) {int n = nums.Length;int[] dp = new int[n];// 使用SortedSet模拟优先队列,存储 (得分, 下标)var pq = new SortedSet<(int score, int index)>(Comparer<(int score, int index)>.Create((a, b) => {int cmp = b.score.CompareTo(a.score); // 按得分降序return cmp != 0 ? cmp : a.index.CompareTo(b.index); // 得分相同时按下标升序}));// 初始状态dp[0] = nums[0];pq.Add((dp[0], 0));for (int i = 1; i < n; i++) {// 移除超出跳跃范围的元素while (pq.Count > 0 && pq.Min.index < i - k) {pq.Remove(pq.Min);}// 计算当前位置的最大得分dp[i] = pq.Min.score + nums[i];// 将当前位置的得分加入集合pq.Add((dp[i], i));}return dp[n - 1];}
}

Java 实现 - 单调队列(最优解)

import java.util.*;class Solution {public int maxResult(int[] nums, int k) {int n = nums.length;int[] dp = new int[n];// 单调递减队列,存储 [得分, 下标]Deque<int[]> deque = new ArrayDeque<>();// 初始状态dp[0] = nums[0];deque.offerLast(new int[]{dp[0], 0});for (int i = 1; i < n; i++) {// 移除超出跳跃范围的元素while (!deque.isEmpty() && deque.peekFirst()[1] < i - k) {deque.pollFirst();}// 计算当前位置的最大得分(队首是最大值)dp[i] = deque.peekFirst()[0] + nums[i];// 维护单调递减性质:移除队尾所有小于等于当前得分的元素while (!deque.isEmpty() && deque.peekLast()[0] <= dp[i]) {deque.pollLast();}// 将当前位置的得分加入队列deque.offerLast(new int[]{dp[i], i});}return dp[n - 1];}
}

C# 实现 - 单调队列(最优解)

using System;
using System.Collections.Generic;public class Solution {public int MaxResult(int[] nums, int k) {int n = nums.Length;int[] dp = new int[n];// 单调递减队列,存储 (得分, 下标)var deque = new LinkedList<(int score, int index)>();// 初始状态dp[0] = nums[0];deque.AddLast((dp[0], 0));for (int i = 1; i < n; i++) {// 移除超出跳跃范围的元素while (deque.Count > 0 && deque.First.Value.index < i - k) {deque.RemoveFirst();}// 计算当前位置的最大得分(队首是最大值)dp[i] = deque.First.Value.score + nums[i];// 维护单调递减性质:移除队尾所有小于等于当前得分的元素while (deque.Count > 0 && deque.Last.Value.score <= dp[i]) {deque.RemoveLast();}// 将当前位置的得分加入队列deque.AddLast((dp[i], i));}return dp[n - 1];}
}

复杂度分析

优先队列方法

  • 时间复杂度:O(n log n),每个元素最多入队和出队一次,每次操作需要 O(log n) 时间
  • 空间复杂度:O(n),优先队列最多存储 n 个元素

单调队列方法

  • 时间复杂度:O(n),每个元素最多入队和出队一次,每次操作需要 O(1) 时间
  • 空间复杂度:O(k),队列最多存储 k 个元素
http://www.xdnf.cn/news/677665.html

相关文章:

  • AI Agent开发第75课-数据、张量、流水线并行全解析
  • 【Web应用】若依:基础篇03-入门案例,若依代码生成器生成前后端代码
  • Web通信协议全景解析:从HTTP到WebService的技术演进与对比
  • 如何寻找大模型在企业业务中的价值?
  • Anaconda下载安装+配置虚拟环境保姆级教程(2025版)
  • 实时数仓flick+clickhouse启动命令
  • 第一个ASP.NET项目
  • 【Elasticsearch】retry_on_conflict
  • Python中while 1和while True有何区别?深入解析无限循环的写法选择
  • 百胜咨询公司:企业EcoVadis认证的专业导航者
  • SIGGRAPH 2025 | 快手可灵团队提出3D感知的电影级文本到视频生成框架CineMaster
  • 鸿蒙5开发宝藏案例分享---一多断点开发实践
  • 0527漏洞原理:SQL注入笔记 SQL注入类型(联合查询注入、报错注入实操)
  • 【本地部署】 Deepseek+Dify创建工作流
  • 【Vue 3 运行时 Diff 算法深度解析:五步走策略实现高效更新】
  • MySQL数据库第一章
  • 科技趋势分析系统 BBC (Big Bang of Computing)
  • mysql中的索引怎么用?
  • [特殊字符]《计算机组成原理》第 8 章 - CPU 的结构和功能
  • 本地部署 DeepSeek
  • 计算机组成原理——指令的寻址方式
  • 迪米特法则 (Law of Demeter, LoD)
  • 多个vue2工程共享node_modules
  • Liunx部署ES单机集群
  • Streamlit 项目知识点总结
  • OpenCv高阶(十三)——人脸检测
  • 第二章:软盘里的90年代
  • 力扣四道题,力扣LCR 016无重复字符的最长子串力扣452.用最小数量的箭引爆气球LCR026.重排链表力扣.1765地图中的最高点
  • 猿大师办公助手WebOffice用二进制数据流在Web前端打开Office文档
  • 如何使用 Redis 实现排行榜功能