1696. 跳跃游戏 VI - 力扣(LeetCode) (leetcode-cn.com)
结果
题意
从数组中从左到右选取数字,下标为0和下标为n-1的为必选。从你上一次选的位置往后选k个数字中的某一个。
第一次的上一次是? 因为0为必选,所以第一次选下标为1~k的数字。
解析
因为要求得分最大,所以每一步我们都希望是当前最大的,这样到终点才能是最大得分。
当前的分数 = 之前的分数 + 数组该位置的数值
数组该位置的数值是无法改变的,所以要让当前的分数最大,就要让之前的分数最大,也就是选取前K个得分最大的那一个
dp[i] = max(nums[i-k]~nums[i-1])
前面这个最大有很多种方法得到。
最容易想到的就是遍历 nums[i-k] ~ nums[i-1] ,并记录最大值
maxn = nums[i-1];
for(int j=i-k;j<i;j++){
maxn = max(maxn, nums[j]);
}
但是我试过了,超时。
所以这里用到滑动窗口
的思想,使用的数据结构是:优先队列(堆)
蓝色的是k = 2(假设一开始在7这个位置)
那么要选取前2个中的最大值,是5
下一步,当前位置来到6,选取前两个最大值,是7
下一步,来到3,选取最大值7
下一步,来到8,选取最大值6
可以观察到每一次的最大值不一定就是下一次的最大值,甚至不会出现在下一次的比较范围
比如说5只会在15和57这两个窗口中存在。
所以比较时不仅要关注数值,也要关注位置,位置不能在 i-k 之前
- 为什么滑动窗口节省时间?
因为如果最大值是在比较范围的中间时,其余的是不需要再比较的。
比如 7 3 5 8 2 4 6 | 9
这里用 | 表示 i 在9这个位置 ,其中k=6(刚好到3)
显然最大值是8,下一次比较时只需要比较8和9,而其他的一定不会比8大(否则8就不是窗口内的最大值了)
而最大值是边缘时,确实需要再比较一次,因为堆的特性,得到窗口内的最大值很快。
代码
- len 指的是数组的大小
- num 是自定义数据结构,其中val表示数值,idx表示该数值的位置
struct num{
num(int a, int b){
this->val = a;
this->idx = b;
}
int val;
int idx;
};
- cmp是重写的比较器,因为堆中是我们自定义的数据类型,因为要大根堆,所以判定条件是 <=
struct cmp{
bool operator()(num n1, num n2){
return n1.val<=n2.val;
}
};
class Solution {
public:
int len;
int maxResult(vector<int>& nums, int k) {
struct num{
num(int a, int b){
this->val = a;
this->idx = b;
}
int val;
int idx;
};
struct cmp{
bool operator()(num n1, num n2){
return n1.val<=n2.val;
}
};
len = nums.size();
vector<int> dp(len);
dp[0] = nums[0];
priority_queue<num,vector<num>, cmp> heap;
for(int i=1;i<len;i++){
if(!heap.empty()){
// 当前滑动窗口的最大值
num top = heap.top();
// 如果滑动窗口的最大值已经超出了 i-k 的范围,那么将这个最大值弹出(它已经无效了)
while(!heap.empty()&&top.idx<i-k){
heap.pop();
top = heap.top();
}
}
// 将前一个位置的值放入堆中
heap.push(num(dp[i-1], i-1));
dp[i] = heap.top().val;
dp[i] += nums[i];
}
return dp[len-1];
}
};