leetcode - 1696 - 跳跃游戏 VI - 动态规划 - 线段树

欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。

题目描述

[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

Related Topics
  • 动态规划
  • 线段树

题目剖析&信息挖掘

此题为动态规划题与,基本思想与下面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/ 最长递增子序列


本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值