窗口最大值和最小值更新结构
一.滑动窗口的最大值⭐⭐⭐⭐⭐
1.对应letecode链接:
[(https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/)]
2.题目描述:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kuHujuMr-1651025054835)(C:\Users\26047\AppData\Roaming\Typora\typora-user-images\image-20220427085016392.png)]
3.解题思路:
方法一:暴力枚举⭐⭐⭐
根据题目意思我们定义一个左指针和一个右指针.左指针在左边右指针控制右边界,控制保持一个窗口为k的大小每次遍历去找窗口里面的最大值。对应时间复杂度O(N^2)
对应代码:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int>ans;//收集答案
int L=0;
int R=k-1;
int N=nums.size();
//窗口往右滑动
while(R<N)
{
int Max=nums[L];
for(int i=L+1;i<=R;i++)
{
Max=max(Max,nums[i]);
}
ans.push_back(Max);
L++;//窗口滑动
R++;
}
return ans;
}
};
方法二:窗口内最大值更新结构⭐⭐⭐⭐⭐
首先我们以 [1,3,-1,-3,5,3,6,7]为例看看什么是滑动窗口。从数组中第一个元素开始遍历,由于窗口的大小是3,因此当遍历到第三个元素时,窗口就形成了。
△ 窗口形成
之后继续遍历元素时,为了保持窗口的大小为3,左侧元素就需要从窗口中剔除。这样使得窗口一直在向右移动,直到考察到最后一个元素结束,这就是创说之中的滑动窗口
😁窗口滑动
我们可以发现在上述滑动窗口形成及移动的过程中,元素是从窗口的右侧进入的,又由于窗口大小是固定的,因此多余的元素是从窗口左侧移除的。 一端进入,另一端出,这不就是队列的性质吗?所以,该题目可以借助队列来求解。下面以数组[5, 3, 4, 1]为例。开始遍历nums = [5, 3, 4, 1]。当right指向第一个元素5时,此时队列为空,将第一个元素5入队。
3入队列
注意由于我们要求的是窗口里面的最大值所以我们需要保证队列是严格的单调递减的。继续考察第二个元素3,此时3小于队列末尾的元素5,因此元素3入队,以便看其是否是下一个窗口的最大值。这时队列中元素从队首到队尾是递减的。
4入队列
此时我们发现4如果进来的话会破坏队列是单调递减的特性。因此我们需要将队列末尾的3弹出
重复上述操作当每一次窗口形成时队列头部的数据就是窗口的最大值.注意窗口内数据的含义表示:当窗口开始缩小时那些位置可能成为最大值。
对应代码:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int>qmax;//单调队列
vector<int>ans;//记录答案
//窗口的含义表示如果窗口依次缩小那些数会依次成为最大值
for(int right=0;right<nums.size();right++)
{
//相等也要将其弹出因为他的小标比你晚过期不需要留你
while(!qmax.empty()&&nums[qmax.back()]<=nums[right])
{
qmax.pop_back();//窗口是依次递减的
}
qmax.push_back(right);//单调队列里面存的是数组小标
if(qmax.front()==right-k)//头部数据已经过期了比如说k为3,i为3
{//检测头部数据是否是过期的数据
qmax.pop_front();
}
if(right>=k-1)//此时窗口已经形成
{
ans.push_back(nums[qmax.front()]);
//头部数据就是形成窗口的最大值
}
}
return ans;
}
};
二.最大值减去最小值小于或等于num的子数组数量⭐⭐⭐⭐⭐⭐
1.对应OJ链接:
2.题目描述:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ueT7Ulc6-1651025054842)(C:\Users\26047\AppData\Roaming\Typora\typora-user-images\image-20220427093724858.png)]
3.解题思路:
方法一:暴力枚举⭐⭐
枚举所有子数组,在遍历这个子数组看他的最大值和最小值相减是否小于等于num如果是将答案记入答案中。当我们将所有子数组都枚举完成之后答案也就正确的求出来了。
对应代码:
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n;
int num;
cin>>n>>num;
vector<int>arr(n);
for(int i=0;i<n;i++){
cin>>arr[i];
}
int Count=0;//记录个数
for(int L=0;L<n;L++){
for(int R=L;R<n;R++){
int Max=arr[L];
int Min=arr[L];
for(int i=L+1;i<=R;i++){//遍历子数组寻找最小值和最大值
Max=max(Max,arr[i]);
Min=min(Min,arr[i]);
}
if(Max-Min<=num){//判断是否达标
Count++;
}
}
}
cout<<Count<<endl;
}
方法二:窗口内最大值和最小值更新结构⭐⭐⭐⭐⭐⭐
有了上一题的基础这一题就会容易很多。如果一个子数组中最大值减最小值已经超过了num那么如果我们将其的访问扩大那么它所有的子数组一定不达标。我们只需要枚举必须以某个位置开头小于等于num的子数组有多少个将答案累加即可,问题我们如何快速得到最大值和最小值了。这就要使用我们上面提过的窗口内最大值更新结构,窗口内最小值更新结构也是同理的,只要窗口内最大值减去最小值没有超过num我们就让窗口一直往右扩直到不达标了,记录答案。换下一个位置开始继续求答案最近直到右边界越界答案就全部求出来了具体请看代码
对应代码:
#include<iostream>
#include<deque>
#include<vector>
using namespace std;
int main()
{
int n,num;
cin>>n>>num;
vector<int>arr(n);
for(int i=0;i<n;i++)
{
cin>>arr[i];
}
int cnt=0;
deque<int>qmax;
deque<int>qmin;
int R=0;
for(int L=0;L<n;L++)//枚举以每个位置开头能求多少答案
{
while(R<n)
{
while(!qmax.empty()&&arr[qmax.back()]<=arr[R])
{
qmax.pop_back();
}
qmax.push_back(R);
while(!qmin.empty()&&arr[qmin.back()]>=arr[R])
{
qmin.pop_back();
}
qmin.push_back(R);
if(arr[qmax.front()]-arr[qmin.front()]>num)//判断是否达标
{
break;
}
else{
R++;//达标继续往右扩直到不达标为止
}
}
cnt+=(R-L);//累加答案
if(L==qmax.front())//头部数据已经过期
{
qmax.pop_front();
}
if(L==qmin.front())//头部数据过期
{
qmin.pop_front();
}
}
cout<<cnt<<endl;
return 0;
}
==qmax.front())//头部数据已经过期
{
qmax.pop_front();
}
if(L==qmin.front())//头部数据过期
{
qmin.pop_front();
}
}
cout<<cnt<<endl;
return 0;
}