高级农民
- 积分
- 4019
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2017-1-22
- 最后登录
- 1970-1-1
|
本帖最后由 Myron2017 于 2026-2-4 21:41 编辑
LC 2297. Jump Game VIII
跳跃规则转化为单调栈维护,是单调栈 + DP 的高级应用!
Jump Game VIII 完整总结
题目核心
目标:从索引 0 跳到索引 n-1,求最小代价
跳跃规则:从位置 i 跳到位置 j(i < j),满足以下两种情况之一:
跳跃类型高度条件中间元素条件形象理解向上跳nums[i] ≤ nums[j]中间所有 nums[k] < nums[i]爬山,中间不能有更高的峰向下跳nums[i] > nums[j]中间所有 nums[k] ≥ nums[i]滑坡,中间不能有凹陷
解题思路演进
1️⃣ 暴力 DP(会超时)
python# O(n³) - 太慢
for i in range(n):
for j in range(i+1, n):
# 检查能否从i跳到j
if can_jump(i, j): # O(n) 检查中间元素
dp[j] = min(dp[j], dp[i] + costs[j])
问题:每次都要遍历中间元素验证条件
2️⃣ 单调栈优化(通过)
核心思想:反向思考
❌ 不问:"从 i 能跳到哪?"
✅ 而问:"谁能跳到 i?"
单调栈的作用:
维护候选起点
自动验证中间元素条件
弹出时就是合法跳跃
python# O(n) - 每个元素最多入栈出栈各1次
for i in range(n):
# 递增栈处理向上跳
while inc_stack and nums[inc_stack[-1]] <= nums[i]:
j = inc_stack.pop()
dp[i] = min(dp[i], dp[j] + costs[i])
# 递减栈处理向下跳
while dec_stack and nums[dec_stack[-1]] > nums[i]:
j = dec_stack.pop()
dp[i] = min(dp[i], dp[j] + costs[i])
inc_stack.append(i)
dec_stack.append(i)
```
---
## 单调栈工作原理
### 递增栈(处理向上跳)
**维护的序列**:
```
栈底 → 栈顶:值递减
inc_stack = [i1, i2, i3]
栈内值 = [大, 中, 小]
```
**弹出规则**:当 `nums[栈顶] ≤ nums[当前]`
**自动保证**:
- ✅ 高度条件:`nums[弹出元素] ≤ nums[当前]`
- ✅ 中间元素:栈的递减性质 → 中间元素都 < 起点
**例子**:
```
nums = [5, 3, 2, 6]
栈变化:
i=0: [0] 值=[5]
i=1: [0,1] 值=[5,3] (3<5, 不弹出)
i=2: [0,1,2] 值=[5,3,2] (2<3, 不弹出)
i=3: 弹出2 → 2可以跳到3 (2≤6 ✅, 中间无)
弹出1 → 1可以跳到3 (3≤6 ✅, 中间2<3 ✅)
弹出0 → 0可以跳到3 (5≤6 ✅, 中间1,2都<5 ✅)
```
---
### 递减栈(处理向下跳)
**维护的序列**:
```
栈底 → 栈顶:值递增
dec_stack = [i1, i2, i3]
栈内值 = [小, 中, 大]
```
**弹出规则**:当 `nums[栈顶] > nums[当前]`
**自动保证**:
- ✅ 高度条件:`nums[弹出元素] > nums[当前]`
- ✅ 中间元素:栈的递增性质 → 中间元素都 ≥ 起点
**例子**:
```
nums = [2, 5, 7, 3]
栈变化:
i=0: [0] 值=[2]
i=1: [0,1] 值=[2,5] (2<5, 不弹出)
i=2: [0,1,2] 值=[2,5,7] (5<7, 不弹出)
i=3: 弹出2 → 2可以跳到3 (7>3 ✅, 中间无)
弹出1 → 1可以跳到3 (5>3 ✅, 中间7≥5 ✅)
不弹0 → (2<3, 不满足向下跳)
为什么单调栈有效?
传统暴力验证
python# 检查从i能否跳到j
for k in range(i+1, j):
if 向上跳 and nums[k] >= nums[i]:
return False # 有障碍
if 向下跳 and nums[k] < nums[i]:
return False # 有凹陷
```
### 单调栈隐式验证
| 检查项 | 单调栈如何保证 |
|--------|--------------|
| 高度条件 | 弹出条件就是高度条件 |
| 中间元素 | **栈的单调性**自动满足 |
**核心洞察**:
```
递增栈:栈底→栈顶递减
→ 后面的元素比前面小
→ 中间元素 < 起点 ✅
递减栈:栈底→栈顶递增
→ 后面的元素比前面大
→ 中间元素 ≥ 起点 ✅
You are given a 0-indexed integer array nums of length n. You are initially standing at index 0. You can jump from index i to index j where i < j if:
nums[i] <= nums[j] and nums[k] < nums[i] for all indexes k in the range i < k < j, or
nums[i] > nums[j] and nums[k] >= nums[i] for all indexes k in the range i < k < j.
You are also given an integer array costs of length n where costs[i] denotes the cost of jumping to index i.
Return the minimum cost to jump to the index n - 1.
Example 1:
Input: nums = [3,2,4,4,1], costs = [3,7,6,4,2]
Output: 8
Explanation: You start at index 0.
- Jump to index 2 with a cost of costs[2] = 6.
- Jump to index 4 with a cost of costs[4] = 2.
The total cost is 8. It can be proven that 8 is the minimum cost needed.
Two other possible paths are from index 0 -> 1 -> 4 and index 0 -> 2 -> 3 -> 4.
These have a total cost of 9 and 12, respectively.
Example 2:
Input: nums = [0,1,2], costs = [1,1,1]
Output: 2
Explanation: Start at index 0.
- Jump to index 1 with a cost of costs[1] = 1.
- Jump to index 2 with a cost of costs[2] = 1.
The total cost is 2. Note that you cannot jump directly from index 0 to index 2 because nums[0] <= nums[1].- class Solution:
- def minCost(self, nums: List[int], costs: List[int]) -> int:
- n = len(nums)
-
- # dp[i] = 到达位置i的最小cost
- dp = [float('inf')] * n
- dp[0] = 0 # 起点cost为0
-
- # 递增栈:找向上跳的目标(nums[j] >= nums[i])
- inc_stack = []
- # 递减栈:找向下跳的目标(nums[j] < nums[i])
- dec_stack = []
-
- for i in range(n):
- # 处理递增栈:从栈中弹出所有 <= nums[i] 的元素
- # 这些元素可以向上跳到i
- while inc_stack and nums[inc_stack[-1]] <= nums[i]:
- j = inc_stack.pop()
- dp[i] = min(dp[i], dp[j] + costs[i])
-
- # 处理递减栈:从栈中弹出所有 > nums[i] 的元素
- # 这些元素可以向下跳到i
- while dec_stack and nums[dec_stack[-1]] > nums[i]:
- j = dec_stack.pop()
- dp[i] = min(dp[i], dp[j] + costs[i])
-
- # 将当前索引加入两个栈
- inc_stack.append(i)
- dec_stack.append(i)
-
- return dp[-1]
复制代码 |
|