欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。
题目描述
[1696] 跳跃游戏 VI
- https://leetcode-cn.com/problems/jump-game-vi/
给你一个下标从 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 <= 10^5
-10^4 <= nums[i] <= 10^4
- 动态规划
- 线段树
题目剖析&信息挖掘
此题为动态规划题与,基本思想与下面2题类似。
https://leetcode-cn.com/problems/climbing-stairs/
跳台阶
https://leetcode-cn.com/problems/min-cost-climbing-stairs/
花最小力气爬楼梯
由于数据规模增加,需要用到线段树优化查询效率。
解题思路
方法一 动态规划+线段树
分析
- 先把题目转变一下,由往后跳改成往前跳。最后终点在0。根据题意不影响最优解。
- 使用设置dp(i) 表示从第i个位置跳到0所得到的最大数。
- dp(0)=nums[0]
- dp(i) = nums[i] +max(dp(j)) (j = [i-k, i-1])
- 原始做法是
getDpList(nums, k) {
dp[0] = nums[0];
for i=1;i<len(nums);i++ {
maxPreVal := IntMin
for j:=i-k;j<=i-1;j++ {
maxPreVal=max(maxPreVal, dp[j])
}
dp[i] = nums[i]+maxPreVal
}
}
// 以上做法2层遍历,复杂度O(n*k), k 最大可达10^5, 最终复杂度为O(n^2) 不满足需求
思路优化
- 上述算法不满足的原因是在查询max(dp(j)) (j = [i-k, i-1])复杂度过高。
- 其实这个是一个区间求最值问题,之前学过RMQ, 线段树可以解决,由于RMQ只是针对静态表的。所以用线段树。
type node struct {
l, r int // 代表树结点代表的区间范围
leftChild, rightChild *node
maxVal int
}
type SegmentTree struct {
nodes []node // 事先申请结点,加事内存分配
root int //根结点编号
}
// 初始化线段树,分配内存大小, 构造树型
func (tree *SegmentTree) Init(l, r int) {
}
// 构造树型
func (tree *SegmentTree) buildNode(l, r, root int) *node {
}
func (tree *SegmentTree) InsertSegment(l, r, weight int) {
tree.insert(l, r, weight, tree.root)
}
func (tree *SegmentTree) insert(l, r, weight, root int) {
}
func (tree *SegmentTree) Query(l, r int) int {
return tree.query(l, r, tree.root)
}
func (tree *SegmentTree) query(l, r, root int) int {
return max(leftVal, rightVal)
}
func max(a, b int) int {
}
func maxResult(nums []int, k int) int {
seg := &SegmentTree{}
seg.Init(0, len(nums))
dp := make([]int, len(nums)+10)
for i, v := range nums {
if i == 0 {
dp[0] = v
} else {
maxPreVal := seg.Query(i-k, i-1) // 查询 max(dp(j)) j= [i-k, i-1]
dp[i] = v+maxPreVal
}
seg.InsertSegment(i, i, dp[i]) // 添加入dp值
}
return dp[len(nums)-1]
}
注意
- 负数也要返回。
- 需要从前往后计算dp值。
知识点
- 动态规划
- 线段树
复杂度
- 时间复杂度:O(nlog(n))
- 空间复杂度:O(n)
参考
代码实现
type node struct {
l, r int // 代表树结点代表的区间范围
leftChild, rightChild *node
maxVal int
}
type SegmentTree struct {
nodes []node // 事先申请结点,加事内存分配
root int //根结点编号
}
// 初始化线段树,分配内存大小, 构造树型
func (tree *SegmentTree) Init(l, r int) {
tree.nodes = make([]node, (r-l+1)*4)
tree.root = 1 //
tree.buildNode(l, r, tree.root)
}
// 构造树型
func (tree *SegmentTree) buildNode(l, r, root int) *node {
if l > r {
return nil
}
mid := (l + r) >> 1
tree.nodes[root].l, tree.nodes[root].r = l, r
tree.nodes[root].maxVal = 0
if l == r {
return &tree.nodes[root]
}
// 构造左右子树
tree.nodes[root].leftChild = tree.buildNode(l, mid, root<<1)
tree.nodes[root].rightChild = tree.buildNode(mid+1, r, root<<1|1)
return &tree.nodes[root]
}
func (tree *SegmentTree) InsertSegment(l, r, weight int) {
tree.insert(l, r, weight, tree.root)
}
func (tree *SegmentTree) insert(l, r, weight, root int) {
if l > tree.nodes[root].r || r < tree.nodes[root].l {
return
}
if l <= tree.nodes[root].l && tree.nodes[root].r <= r {
tree.nodes[root].maxVal = weight
return
}
tree.insert(l, r, weight, root<<1)
tree.insert(l, r, weight, root<<1|1)
/*
更新本区间的最大值
*/
tree.nodes[root].maxVal = max(tree.nodes[root<<1].maxVal , tree.nodes[root<<1|1].maxVal)
}
func (tree *SegmentTree) Query(l, r int) int {
return tree.query(l, r, tree.root)
}
func (tree *SegmentTree) query(l, r, root int) int {
if l > tree.nodes[root].r || r < tree.nodes[root].l {
return math.MinInt32
}
if l <= tree.nodes[root].l && tree.nodes[root].r <= r {
return tree.nodes[root].maxVal
}
leftVal := tree.query(l, r, root<<1)
rightVal := tree.query(l, r, root<<1|1)
return max(leftVal, rightVal)
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func maxResult(nums []int, k int) int {
seg := &SegmentTree{}
seg.Init(0, len(nums))
dp := make([]int, len(nums)+10)
for i, v := range nums {
if i == 0 {
dp[0] = v
} else {
maxPreVal := seg.Query(i-k, i-1) // 查询 max(dp(j)) j= [i-k, i-1]
dp[i] = v+maxPreVal
}
seg.InsertSegment(i, i, dp[i]) // 添加入dp值
}
return dp[len(nums)-1]
}
相关题目
https://leetcode-cn.com/problems/climbing-stairs/ 跳台阶
https://leetcode-cn.com/problems/min-cost-climbing-stairs/ 花最小力气爬楼梯
https://leetcode-cn.com/problems/longest-increasing-subsequence/ 最长递增子序列
本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。