目录
跳跃游戏最大得分 —— 动态规划与单调队列优化详解
题目描述
给定一个下标从 0 开始的整数数组 nums 和一个整数 k,你最初位于数组的第一个元素(下标为 0)。
每一步,你最多可以向前跳跃 k 步,但不能跳出数组边界。也就是说,从当前下标 i,你可以跳到区间 [i+1, min(n-1, i+k)] 中的任意位置。
你的目标是跳到数组的最后一个位置(下标为 n-1),并且你获得的分数是你经过的所有数字的和。
请返回你能获得的最大得分。
解题分析
这道题本质上是一个动态规划问题,我们要在满足跳跃范围的条件下,找到一条路径使得经过的元素和最大。
- 状态定义
设dp[i]表示跳到位置i时能获得的最大分数。 - 初始状态
dp[0] = nums[0],因为起点就是数组的第一个元素。
状态转移
从 i 的前面 k 个位置中选一个能让 dp[j] 最大的跳跃过来,再加上 nums[i]。
即:
- dp[i]=maxj∈[i−k,i−1]dp[j]+nums[i]dp[i] = \max_{j \in [i-k, i-1]} dp[j] + nums[i]
- 目标
求出dp[n-1],即跳到数组最后一个位置能获得的最大得分。
解题方法
1. 朴素动态规划
直接按照状态转移方程计算:
for i in range(1, n):
dp[i] = max(dp[j] for j in range(i-k, i)) + nums[i]
- 时间复杂度:
每个i都要查询k个状态,整体复杂度为 O(n×k)O(n \times k)。 - 缺点:
当n和k都很大时,效率低下,无法通过大规模测试。
2. 单调队列优化
注意到在计算 dp[i] 时,我们只关心区间 [i-k, i-1] 内 dp[j] 的最大值。
- 使用一个双端队列
dq,存储下标,保证dp[dq[0]]是当前窗口最大值。 - 窗口向右滑动时,维护队列中元素的有效性(排除超过跳跃范围的下标)。
- 维护队列单调递减,使得队头永远是最大值的索引。
代码实现
from collections import deque
class Solution:
def maxResult(self, nums: List[int], k: int) -> int:
n = len(nums)
dp = [0] * n
dp[0] = nums[0]
dq = deque([0]) # 存放索引,保持dp值单调递减
for i in range(1, n):
# 弹出不再窗口范围内的索引
while dq and dq[0] < i - k:
dq.popleft()
# dp[i]由窗口最大dp值加nums[i]转移
dp[i] = dp[dq[0]] + nums[i]
# 维护单调递减队列
while dq and dp[dq[-1]] <= dp[i]:
dq.pop()
dq.append(i)
return dp[-1]
复杂度分析与对比
| 方法 | 时间复杂度 | 空间复杂度 | 适用情况 |
| 朴素动态规划 | O(n×k)O(n \times k) | O(n)O(n) | kk 和 nn 较小 |
| 单调队列优化 | O(n)O(n) | O(n)O(n) | 适用于大规模数据,效率高 |
示例说明
假设输入:
nums = [1, -1, -2, 4, -7, 3]
k = 2
执行过程:
- 初始化:
dp[0] = 1 - 计算
dp[1]:从dp[0]跳,最大是1 + (-1) = 0 - 计算
dp[2]:从dp[0], dp[1]中选最大,max(1,0) + (-2) = -1 - 计算
dp[3]:从dp[1], dp[2]中选最大,max(0, -1) + 4 = 4 - 计算
dp[4]:从dp[2], dp[3]中选最大,max(-1, 4) + (-7) = -3 - 计算
dp[5]:从dp[3], dp[4]中选最大,max(4, -3) + 3 = 7
最终结果是 dp[5] = 7。
总结
- 本题通过动态规划解决跳跃路径最大和问题。
- 直接动态规划时间复杂度高,采用单调队列优化窗口最大值查询使复杂度降至 O(n)O(n)。
- 单调队列是处理滑动窗口最大值问题的经典技巧,能显著提升效率。
774

被折叠的 条评论
为什么被折叠?



