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

LeetCode 2297. 跳跃游戏 VIII(中等)

题目描述

给定一个长度为 n 的下标从 0 开始的整数数组 nums。初始位置为下标 0。当 i < j 时,你可以从下标 i 跳转到下标 j:

  • 对于在 i < k < j 范围内的所有下标 k 有 nums[i] <= nums[j] 和 nums[k] < nums[i] , 或者
  • 对于在 i < k < j 范围内的所有下标 k 有 nums[i] > nums[j] 和 nums[k] >= nums[i] 。

你还得到了一个长度为 n 的整数数组 costs,其中 costs[i] 表示跳转下标 i 的代价。

返回跳转到下标 n - 1 的最小代价。

示例 1:

输入: nums = [3,2,4,4,1], costs = [3,7,6,4,2]
输出: 8
解释: 从下标 0 开始。
- 以 costs[2]= 6 的代价跳转到下标 2。
- 以 costs[4]= 2 的代价跳转到下标 4。
总代价是 8。可以证明,8 是所需的最小代价。
另外两个可能的路径是:下标 0 -> 1 -> 4 和下标 0 -> 2 -> 3 -> 4。
它们的总代价分别为9和12。

示例 2:

输入: nums = [0,1,2], costs = [1,1,1]
输出: 2
解释: 从下标 0 开始。
- 以 costs[1] = 1 的代价跳转到下标 1。
- 以 costs[2] = 1 的代价跳转到下标 2。
总代价是 2。注意您不能直接从下标 0 跳转到下标 2,因为 nums[0] <= nums[1]。

解释:

  • n == nums.length == costs.length
  • 1 <= n <= 10^5
  • 0 <= nums[i], costs[i] <= 10^5

问题分析

这道题是跳跃游戏系列中的一道具有挑战性的题目。关键在于理解跳跃条件:

跳跃条件解析:

  • 条件一: nums[i] <= nums[j] 且中间所有 nums[k] < nums[i]

    • 含义:从位置i跳到右侧第一个 ≥ nums[i] 的位置

    • 对应:单调递减栈(维护一个从栈底到栈顶递减的序列)

  • 条件二: nums[i] > nums[j] 且中间所有 nums[k] >= nums[i]

    • 含义:从位置i跳到右侧第一个 < nums[i] 的位置

    • 对应:单调递增栈(维护一个从栈底到栈顶递增的序列)

这两个条件实际上是互补的,可以用单调栈来高效处理。


解题思路

单调栈 + 动态规划

核心思想:

  1. 使用两个单调栈分别处理两种跳跃条件
  2. 用动态规划记录到达每个位置的最小代价
  3. 从左到右遍历,动态更新可达位置的最小代价

算法步骤:

  • 初始化 dp[i] 表示到达位置 i 的最小代价,dp[0] = 0
  • 维护两个单调栈:
    • stack1:处理条件一(单调递减栈)
    • stack2:处理条件二(单调递增栈)
  • 对每个位置 j,检查能从哪些位置跳到 j,并更新最小代价

算法过程

输入: nums = [3,2,4,4,1], costs = [3,7,6,4,2]

初始状态

位置:    0  1  2  3  4
nums:   [3, 2, 4, 4, 1]
costs:  [3, 7, 6, 4, 2]
dp:     [0, ∞, ∞, ∞, ∞]stack1: []  (单调递减栈 - 处理条件一)
stack2: []  (单调递增栈 - 处理条件二)

步骤1:处理位置0 (nums[0] = 3)

当前位置 j = 0, nums[0] = 3stack1操作:
- 栈为空,直接入栈
- stack1: [0]stack2操作:
- 栈为空,直接入栈  
- stack2: [0]dp状态:[0, ∞, ∞, ∞, ∞]

步骤2:处理位置1 (nums[1] = 2)

当前位置 j = 1, nums[1] = 2stack1操作(条件一):
- nums[stack1.top()] = nums[0] = 3 > nums[1] = 2
- 不满足 nums[i] <= nums[j],不弹出
- stack1: [0, 1]stack2操作(条件二):
- nums[stack2.top()] = nums[0] = 3 > nums[1] = 2 ✓
- 弹出位置0,更新 dp[1] = min(∞, dp[0] + costs[1]) = min(∞, 0 + 7) = 7
- 将位置1入栈
- stack2: [1]dp状态:[0, 7, ∞, ∞, ∞]跳跃路径:0 → 1 (代价7)

步骤3:处理位置2 (nums[2] = 4)

当前位置 j = 2, nums[2] = 4stack1操作(条件一):
- nums[1] = 2 <= nums[2] = 4 ✓,弹出位置1dp[2] = min(∞, dp[1] + costs[2]) = min(∞, 7 + 6) = 13
- nums[0] = 3 <= nums[2] = 4 ✓,弹出位置0  dp[2] = min(13, dp[0] + costs[2]) = min(13, 0 + 6) = 6
- 将位置2入栈
- stack1: [2]stack2操作(条件二):
- nums[1] = 2 < nums[2] = 4,不满足条件二,不弹出
- 将位置2入栈
- stack2: [1, 2]dp状态:[0, 7, 6, ∞, ∞]跳跃路径:0 → 2 (代价6) 或 0 → 1 → 2 (代价13)
最优:0 → 2 (代价6)

步骤4:处理位置3 (nums[3] = 4)

当前位置 j = 3, nums[3] = 4stack1操作(条件一):
- nums[2] = 4 <= nums[3] = 4 ✓,弹出位置2dp[3] = min(∞, dp[2] + costs[3]) = min(∞, 6 + 4) = 10
- 将位置3入栈
- stack1: [3]stack2操作(条件二):
- nums[2] = 4 不大于 nums[3] = 4,不弹出
- 将位置3入栈
- stack2: [1, 2, 3]dp状态:[0, 7, 6, 10, ∞]跳跃路径:0 → 2 → 3 (代价10)

步骤5:处理位置4 (nums[4] = 1)

当前位置 j = 4, nums[4] = 1stack1操作(条件一):
- nums[3] = 4 > nums[4] = 1,不满足条件一,不弹出
- 将位置4入栈
- stack1: [3, 4]stack2操作(条件二):
- nums[3] = 4 > nums[4] = 1 ✓,弹出位置3dp[4] = min(∞, dp[3] + costs[4]) = min(∞, 10 + 2) = 12
- nums[2] = 4 > nums[4] = 1 ✓,弹出位置2dp[4] = min(12, dp[2] + costs[4]) = min(12, 6 + 2) = 8
- nums[1] = 2 > nums[4] = 1 ✓,弹出位置1dp[4] = min(8, dp[1] + costs[4]) = min(8, 7 + 2) = 9
- 最终 dp[4] = 8
- stack2: [4]dp状态:[0, 7, 6, 10, 8]跳跃路径:0 → 2 → 4 (代价8)

最优路径

最终答案:dp[4] = 8最优路径:0 → 2 → 4
详细分析:
- 从位置0跳到位置2:满足条件一 (nums[0]=3 <= nums[2]=4,中间nums[1]=2 < nums[0]=3)
- 从位置2跳到位置4:满足条件二 (nums[2]=4 > nums[4]=1,中间nums[3]=4 >= nums[2]=4)
- 总代价:costs[2] + costs[4] = 6 + 2 = 8

详细代码实现

Java 实现

class Solution {public long minCost(int[] nums, int[] costs) {int n = nums.length;long[] dp = new long[n];// 初始化dp数组Arrays.fill(dp, Long.MAX_VALUE);dp[0] = 0;  // 起始位置代价为0// 两个单调栈Deque<Integer> stack1 = new ArrayDeque<>();  // 处理条件一Deque<Integer> stack2 = new ArrayDeque<>();  // 处理条件二for (int j = 0; j < n; j++) {// 处理条件一:nums[i] <= nums[j] 且中间元素都小于 nums[i]// 单调递减栈,当遇到更大的元素时弹出while (!stack1.isEmpty() && nums[stack1.peek()] <= nums[j]) {int i = stack1.pop();dp[j] = Math.min(dp[j], dp[i] + costs[j]);}stack1.push(j);// 处理条件二:nums[i] > nums[j] 且中间元素都大于等于 nums[i]// 单调递增栈,当遇到更小的元素时弹出while (!stack2.isEmpty() && nums[stack2.peek()] > nums[j]) {int i = stack2.pop();dp[j] = Math.min(dp[j], dp[i] + costs[j]);}stack2.push(j);}return dp[n - 1];}
}

C# 实现

public class Solution {public long MinCost(int[] nums, int[] costs) {int n = nums.Length;long[] dp = new long[n];// 初始化dp数组for (int i = 0; i < n; i++) {dp[i] = long.MaxValue;}dp[0] = 0;  // 起始位置代价为0// 两个单调栈Stack<int> stack1 = new Stack<int>();  // 处理条件一Stack<int> stack2 = new Stack<int>();  // 处理条件二for (int j = 0; j < n; j++) {// 处理条件一:nums[i] <= nums[j] 且中间元素都小于 nums[i]while (stack1.Count > 0 && nums[stack1.Peek()] <= nums[j]) {int i = stack1.Pop();dp[j] = Math.Min(dp[j], dp[i] + costs[j]);}stack1.Push(j);// 处理条件二:nums[i] > nums[j] 且中间元素都大于等于 nums[i]while (stack2.Count > 0 && nums[stack2.Peek()] > nums[j]) {int i = stack2.Pop();dp[j] = Math.Min(dp[j], dp[i] + costs[j]);}stack2.Push(j);}return dp[n - 1];}
}

时间复杂度和空间复杂度

  • 时间复杂度:O(n) - 每个元素最多入栈和出栈一次
  • 空间复杂度:O(n) - dp数组和两个栈的空间

相关文章:

  • 【电赛培训课程】电子设计竞赛工程基础知识
  • Git常用命令完全指南:从入门到精通
  • 【Redis实战:缓存与消息队列的应用】
  • 30 C 语言递归算法详解:基准条件、递归逻辑、循环对比、经典案例(斐波那契、猴子吃桃、汉诺塔、二分查找等)
  • 防火墙iptables项目实战
  • golang常用库之-go-feature-flag库(特性开关(Feature Flags))
  • 关于面试找工作的总结(四)
  • Linux容器篇、第一章_02Rocky9.5 系统下 Docker 的持久化操作与 Dockerfile 指令详解
  • 电子电路:共集电极放大器原理与作用解析
  • MySQL JSON 查询中的对象与数组技巧
  • nginx配置
  • DeviceNET从站转EtherNET/IP主站在盐化工行业的创新应用
  • STM32L0看门狗设置LL库
  • 《Vuejs设计与实现》第 7 章(渲染器的设计)
  • 一个小小的 flask app, 几个小工具,拼凑一下
  • 【Linux】Linux 环境变量
  • skynet monitor线程的作用
  • Webpack常见的插件和模式
  • STM32最小CLion开发环境
  • MYSQL之表的内连和外连
  • 仪器网站模板/网络营销的常用方法
  • scratch编程网站/手机怎么搭建网站
  • 怎么做同城购物网站/沈阳网站建设
  • 地方旅游网站开发/如何弄一个自己的网站
  • mac可以做网站服务器吗/信息如何优化上百度首页公司
  • 网站制作素材全套/提高工作效率的方法有哪些