⭐算法入门⭐《队列 - 单调队列》中等01 —— LeetCode 1696. 跳跃游戏 VI
Posted 英雄哪里出来
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了⭐算法入门⭐《队列 - 单调队列》中等01 —— LeetCode 1696. 跳跃游戏 VI相关的知识,希望对你有一定的参考价值。
🙉饭不食,水不饮,题必须刷🙉
C语言免费动漫教程,和我一起打卡! 🌞《光天化日学C语言》🌞
LeetCode 太难?先看简单题! 🧡《C语言入门100例》🧡
数据结构难?不存在的! 🌳《画解数据结构》🌳
LeetCode 太简单?算法学起来! 🌌《夜深人静写算法》🌌
一、题目
1、题目描述
给定一个下标从 0 开始的整数数组
nums[]
和一个整数 k k k 。一开始在下标 0 处。每一步,你最多可以往前跳 k k k 步,但不能跳出数组的边界。也就是说,可以从下标 i i i 跳到 [ i + 1 , m i n ( n − 1 , i + k ) ] [i + 1, min(n - 1, i + k)] [i+1,min(n−1,i+k)] 包含 两个端点的任意位置。
目标是到达数组最后一个位置(下标为 n − 1 n - 1 n−1 ),得分 为经过的所有数字之和。请返回能得到的 最大得分。。
样例输入:nums = [1,-1,-2,4,-7,3], k = 2
样例输出:7
2、基础框架
- C语言版本 给出的基础框架代码如下:
int maxResult(int* nums, int numsSize, int k){
}
3、原题链接
( 1 ) (1) (1) LeetCode 1696. 跳跃游戏 VI
二、解题报告
1、思路分析
比较容易想到的是动态规划,假设跳到位置
i
i
i 的最大值是
d
p
[
i
]
dp[i]
dp[i], 可以得到状态转移方程如下:
d
p
[
i
]
=
n
u
m
s
[
i
]
+
m
a
x
(
d
p
[
i
−
1
]
,
.
.
.
,
d
p
[
i
−
k
]
)
dp[i] = nums[i] + max(dp[i-1], ..., dp[i-k])
dp[i]=nums[i]+max(dp[i−1],...,dp[i−k]) 但是这一步的问题在于,数组长度为
n
n
n 时,状态转移的时间为
O
(
k
)
O(k)
O(k),所以整個算法的时间复杂度为
O
(
n
k
)
O(nk)
O(nk)。所以我们需要想办法将
m
a
x
(
d
p
[
i
−
1
]
,
.
.
.
,
d
p
[
i
−
k
]
)
max(dp[i-1], ..., dp[i-k])
max(dp[i−1],...,dp[i−k]) 这步操作化为
O
(
1
)
O(1)
O(1)。
维护一个单调递减队列,这样就能通过
O
(
1
)
O(1)
O(1) 的时间找到从队首找到最大值。单调队列始终保持
d
p
[
.
.
.
]
dp[...]
dp[...] 的元素在队列中是单调递减的。对于队列中的两个元素,下标位置为
i
<
j
i < j
i<j, 如果
d
p
[
i
]
≤
d
p
[
j
]
dp[i] \\le dp[j]
dp[i]≤dp[j],则
d
p
[
i
]
dp[i]
dp[i] 不能放入 单调队列中,因为它不会比
d
p
[
j
]
dp[j]
dp[j] 更优。并且时刻保证,当前元素插入单调队列之后,单调队列队列首的下标
x
x
x,满足
i
−
x
≤
k
i - x \\le k
i−x≤k。
单调队列 是一个 双端队列,特点是 头部只做删除,尾部可以做插入和删除。
2、时间复杂度
- 任何一个元素只会在队列中入队和出队最多一次,总的时间复杂度为 O ( n ) O(n) O(n)。
3、代码详解
/**************************** 顺序表 实现队列 ****************************/
#define DataType int
#define maxn 100005
struct Queue {
DataType data[maxn];
int head, tail;
};
void QueueClear(struct Queue* que) {
que->head = que->tail = 0;
}
void QueueEnqueue(struct Queue *que, DataType dt) {
que->data[ que->tail++ ] = dt;
}
void QueueDequeueFront(struct Queue* que) {
++que->head;
}
void QueueDequeueRear(struct Queue* que) {
--que->tail;
}
DataType QueueGetFront(struct Queue* que) {
return que->data[ que->head ];
}
DataType QueueGetRear(struct Queue* que) {
return que->data[ que->tail - 1 ];
}
int QueueGetSize(struct Queue* que) {
return que->tail - que->head;
}
int QueueIsEmpty(struct Queue* que) {
return !QueueGetSize(que);
}
/**************************** 顺序表 实现队列 ****************************/
int dp[maxn];
int maxResult(int* nums, int numsSize, int k){
int i;
struct Queue *q = (struct Queue *) malloc (sizeof(struct Queue));
dp[0] = nums[0]; // (1)
QueueClear(q);
QueueEnqueue(q, 0); // (2)
for(i = 1; i < numsSize; ++i) { // (3)
while(!QueueIsEmpty(q) && i - QueueGetFront(q) > k) // (4)
QueueDequeueFront(q);
dp[i] = nums[i] + dp[ QueueGetFront(q) ]; // (5)
while(!QueueIsEmpty(q) && dp[ QueueGetRear(q) ] <= dp[i]) // (6)
QueueDequeueRear(q);
QueueEnqueue(q, i); // (7)
}
return dp[numsSize-1];
}
- ( 1 ) (1) (1) 初始化位置;
- ( 2 ) (2) (2) 初始情况,队列里面只有 0 这个位置;
- ( 3 ) (3) (3) 从第 1 个位置开始计算 d p [ i ] dp[i] dp[i];
- ( 4 ) (4) (4) 确保计算过程中的 区间范围在 [ i − 1 , i − k ] [i-1, i-k] [i−1,i−k] 范围内;
-
(
5
)
(5)
(5) 利用单调队列性质,
dp[ QueueGetFront(q) ]
必然最大,直接赋值; - ( 6 ) (6) (6) d p [ i ] dp[i] dp[i] 一定会插入单调队列,所以比它小的都应该出队;
- ( 7 ) (7) (7) 执行插入操作;
三、本题小知识
某些问题初始思路都是动态规划,无论能否用动态规划在规定时间范围内求解,都可以先用状态转移方程把它写出来。有的状态转移方程,再去看哪一步需要优化,从而考虑用什么数据结构进行优化。
以上是关于⭐算法入门⭐《队列 - 单调队列》中等01 —— LeetCode 1696. 跳跃游戏 VI的主要内容,如果未能解决你的问题,请参考以下文章
⭐算法入门⭐《队列 - 单调队列》中等04 —— LeetCode 剑指 Offer 59 - II. 队列的最大值
⭐算法入门⭐《队列 - 单调队列》中等03 —— LeetCode 918. 环形子数组的最大和
⭐算法入门⭐《队列 - 单调队列》困难01 —— LeetCode 239. 滑动窗口最大值
⭐算法入门⭐《队列 - 单调队列》困难02 —— LeetCode1425. 带限制的子序列和