1. 程式人生 > 其它 >leetcode - 1696 - 跳躍遊戲 VI - 動態規劃 - 線段樹

leetcode - 1696 - 跳躍遊戲 VI - 動態規劃 - 線段樹

技術標籤:leetcodeleetcode動態規劃演算法資料結構

歡迎關注更多精彩
關注我,學習常用演算法與資料結構,一題多解,降維打擊。

文章目錄

題目描述

[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/ 最長遞增子序列


本人碼農,希望通過自己的分享,讓大家更容易學懂計算機知識。

在這裡插入圖片描述