1696 跳跃游戏 VI(动态规划、优先队列优化)

1. 问题描述:

给你一个下标从 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 <= 105
  • -104 <= nums[i] <= 104

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game-vi

2. 思路分析:

① 分析题目可以知道我们可以尝试所有从起点到达终点的所有路径,在所有路径中找到经过数字之和的最大的那条路径这个思路是最容易想到也是解决起来最简单的是使用dfs搜索的方法,但是从起点到终点所有的路径的数据量是非常庞大的,所以当数据量超过某一个范围的时候是根本计算不出结果的(不管是dfs还是bfs都是无法解决数据量在10 ^ 5范围的)所以提交上去也是超时的

② 除了搜索所有的路径方法之外,我们想是否可以在递归所有路径的基础上进行优化从而降低某些不必要的搜索呢?答案是肯定的,我们根据递归可以联想到的是记忆性的递归,对于这道题目来说其实就是动态规划,其实动态规划理解起来应该属于记忆型的迭代吧,暴力枚举不管是什么情况都会枚举所有的可能但是动态规划是在暴力的基础上使用数组或者列表等数据结构来记录中间的结果使得我们可以根据这个数据结构记录的上一次结果递推递推出当前状态状态下最优的结果(感觉应该属于优化的暴力枚举吧),动态规划的核心就是dp数组的定义与状态方程式的编写,对于这道题目来说其实思路还是可以想出来的,我们无非是需要利用上一次的结果递推出当前的结果,如果当前我的位置为i,我们是需要从上一个位置到达这个当前的i位置的,而上一个位置存在多个也即对应着多条路径,而题目的限制条件是最多能够在k步的条件下到从一个位置到达另外一个位置所以我的任务是需要计算出j = 1,2,3...k的步数的条件下计算所有的上一个位置到达j这个位置的得分在这些得分中取最大的那个就是到达当前i位置的最大得分,所以很容易得到状态转移方程为:f[i] = max{f[i - j]} + nums[i],其中j = 1, 2...k,dp[i]的含义为到达当前i位置的最大得分,其实简单来说就是我目前得到了当前j位置之前的位置的最大得分,所以我的任务是能够从在到达当前j位置的上一个位置的得分中选择一个最大的得分那么对于所有能够到达j位置的路径来这个得分肯定是最大的,所以最终dp[n - 1]就是能够到达终点的最大得分,其实这一点也很好理解,但是因为是双重循环所以提交上去还是超时了(从第一个位置递推到最后一个位置)

③ 力扣题解中提供了一种优化动态规划的代码,思路挺好的可以学习学习,在动态规划的思路上借助于优先队列来存储所有能够到达当前位置j的上一个位置的最大得分,因为是优先队列所以元素会被赋予优先级我们可以利用这个优先级优先处理上一次能够到达当前位置j的最大得分,因为使用的是python语言所以可以使用PriorityQueue声明一个优先队列,队列的元素类型为元组,这样一个元组可以表达多个信息,元组可以包含两个元素,第一个是优先级别,因为元素值越小优先级越高所以我们可以利用得分的相反数作为优先级,第二个到达当前元素的位置,这样在循环中我们可以在双端队列中移除掉一些不能够到达当前j位置的元素,也即保证队列中的元素都是可以到达j位置的,而使用优先队列队首元素保存就是上一个位置的最大得分,所以我们在循环中可以更新对应的答案更新到最后就是最终的答案

3. 代码如下:

动态规划(超时):

import sys
from typing import List

class Solution:
    def maxResult(self, nums: List[int], k: int) -> int:
        n = len(nums)
        # dp[i]表示到i位置的最大得分
        dp = [-sys.maxsize] * n
        dp[0] = nums[0]
        for i in range(1, n):
            for j in range(1, k + 1):
                if i - j < 0: break
                elif dp[i - j] != -sys.maxsize:
                    # 计算dp[i]的最大值
                    dp[i] = max(dp[i], dp[i - j] + nums[i])
        return dp[n - 1]

优先队列优化:

from typing import List
import queue

class Solution:
    def maxResult(self, nums: List[int], k: int) -> int:
        q = queue.PriorityQueue()
        q.put((-nums[0], 0))
        res = nums[0]
        for i in range(1, len(nums)):
            # 删除不能够到达i位置的元素
            while not q.empty():
                # 移除优先队列中的元素
                top = q.get()
                if i - top[-1] <= k:
                    # 移除了满足条件的元素这个时候需要再一次加入进来
                    q.put(top)
                    break
            # 更新答案, 优先队列中的队首元素就是上一个位置的最大得分
            res = -top[0] + nums[i]
            q.put((-res, i))
        return res

 

你可能感兴趣的:(力扣,动态规划)