【Leetcode】1696. 跳跃游戏 VI——1696
📝 题目
1696. 跳跃游戏 VI
给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。
一开始你在下标 0 处。每一步,你最多可以往前跳 k 步,但你不能跳出数组的边界。也就是说,你可以从下标 i 跳到 [i + 1, min(n - 1, i + k)] 包含 两个端点的任意位置。
你的目标是到达数组最后一个位置(下标为 n - 1 ),你的 得分 为经过的所有数字之和。
请你返回你能得到的 最大得分 。
提示:
1 <= nums.length, k <= 105-104 <= nums[i] <= 104
🔍 思路
1. 常规DP
每个位置可以向前跳k步,那么假定当前位置可以获得的分数为dp[i],那么从当前位置跳到之后k步之内的任意位置,对其可以获得的dp[j]进行更新。据此可以更新完所有位置的收益。
class Solution:
def maxResult(self, nums: List[int], k: int) -> int:
n = len(nums)
dp = [-inf]*n
dp[0] = nums[0]
for i in range(n):
for j in range(1,k+1):
if i+j>=n:break
dp[i+j] = max(dp[i+j],dp[i]+nums[i+j])
return dp[-1]
可以发现上述算法的时间复杂度为O(nk),由于n和k的数量级都比较大,因此时间复杂度是超标的。
2. 最大堆
根据上述分析,反过来说,对于每一个位置而言,其可以获得的最大分数是前k格以内各自得到的分数的最大值跳到当前位置即
\(dp[j]=max(dp[i])+nums[j]\ i>=i-k\)
可以发现实际上对于常规DP,其时间复杂度过高的原因是没必要对于当前位置的得分进行k次更新,只需要找到前k个位置中最大的得分即可。
这里可以使用最大堆实现,在堆中维护当前位置之前k个位置中的最大得分,并及时弹出距离当前位置超过k的得分。
class Solution:
def maxResult(self, nums: List[int], k: int) -> int:
t = []
heappush(t, (-nums[0], 0))
for i in range(1, len(nums)):
while t[0][1] < i - k:
heappop(t)
if i == len(nums) - 1: return -t[0][0] + nums[i]
heappush(t, (t[0][0] - nums[i], i))
return nums[0]
时间复杂度是\(O(n\ logk)\)
3. 单调栈
根据最大堆的实现思路,我们继续深入思考。
在计算得分时,我们只关心滑动窗口内的最大值,而较靠后的位置如果存在更大的得分,那么前序较小的得分在后续计算中一定不会被使用。因此考虑使用单调栈以替换最大堆。
具体而言,对于位置 i 来说:
- 滑动窗口范围是 [i-k, i-1],我们需要从这个范围中找出最大的 dp 值。
- 如果在滑动窗口的末尾出现了一个更大的得分值,那么之前较小的得分值无论在本次还是后续的更新中都不可能被选择,因此可以直接丢弃这些较小的值。
class Solution:
def maxResult(self, nums: List[int], k: int) -> int:
t = deque([(nums[0], 0)])
for i in range(1, len(nums)):
# 弹出出界的
while t[0][1]<i-k:
t.popleft()
cur = nums[i]+t[0][0] # 当前位置的最优解
if i==len(nums)-1:return cur
# 递减的单调栈
while t and t[-1][0]<=cur:
t.pop()
t.append((cur,i))
return t[0][0]

浙公网安备 33010602011771号