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
- 動態規劃
- 線段樹
題目剖析&資訊挖掘
此題為動態規劃題與,基本思想與下面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/ 最長遞增子序列
本人碼農,希望通過自己的分享,讓大家更容易學懂計算機知識。