数据结构之线性结构——单调队列与滑动窗口:算法优化技巧之一

数据结构之线性结构——单调队列与滑动窗口:算法优化技巧之一我们去食堂排队打饭的时候 很想去看看今天有什么菜 但是 能不能看到菜和身高有关 如果前面的人比你高 你就看不到菜了 这时候 你突然发动 超能力 从后往前依次把队列中身高比你高的人全赶跑了 最终当你把队列处理完毕时你再入队 你就是队列中 最

大家好,欢迎来到IT知识分享网。

我们去食堂排队打饭的时候,很想去看看今天有什么菜,但是,能不能看到菜和身高有关。如果前面的人比你高,你就看不到菜了。

这时候,你突然发动“超能力”,从后往前依次把队列中身高比你高的人全赶跑了,最终当你把队列处理完毕时你再入队,你就是队列中“最高”的人了。

假设队伍中的每个人都拥有这个“超能力”,那么每个人进入队伍时,都会将身高比他高的人赶走,最终队伍会满足单调性:

从队头到队尾,依次从矮到高。

上述描述的过程就是单调队列的产生过程。

单调队列介绍

单调队列中的元素是单调有序的,这一点在上文的描述中已经体现。因此,单调队列的特点之一就是维护区间极值

以单调递增的队列为例,队首一直是极小值,因此在区间内查找极值的时间复杂度为O(1)。

我们发现,在创建单调队列时,队尾元素需要能入队出队,队头元素也需要出队(打饭完成出队),因此单调队列一般用双端队列deque来实现

由于数据的不断入队出队,单调队列可以用来维护一段区间内的最值。如果区间长度固定,那就能够维护滑动窗口中的最值,这也是单调队列的经典应用。

在维护单调队列的过程中,每个元素只会入队和出队一次,因此维护单调队列的时间复杂度为O(n)

单调队列的维护过程演示

以队列{12,34,23,78,89,-8,19,55}为例,开始维护单调递增的对应单调队列:

(1)首先12入队,队列中有{12};

(2)34比12大,34入队,更新队列为{12,34};

(3)23比34小,34出队,23再入队,更新队列为{12,23};

(4)78比23大,78入队,更新队列为{12,23,78};

(5)89比78大,89入队,更新队列为{12,23,78,89};

(6)-8比队列中任何元素都小,因此循环将所有元素从队尾出队后,-8入队,更新队列为{-8};

(7)19比-8大,19入队,更新队列为{-8,19};

(8)55比-8大,55入队,更新队列为{-8,19,55};

在维护过程中,始终保持队首元素为当前已出现的所有元素的最小值。

但如果只是为了找一个数列中的最小值,没必要动用单调队列,因此单调队列一般用于在滑动窗口中寻找最值,接下来结合例题1介绍维护单调队列的过程与代码模版。

例题:

滑动窗口 /【模板】单调队列

数据结构之线性结构——单调队列与滑动窗口:算法优化技巧之一

算法解析

1、先不考虑单调队列,使用普通的算法来试试看。从头到尾扫描,每次检查k个数里的最小值和最大值,时间复杂度为O(nk)。

【参考代码】

#include 
  
    #include 
   
     using namespace std; long long a[]; long long maxx[],minn[]; int n,k; int main(){ cin>>n>>k; for(int i=0;i 
    
      >a[i]; } for(int i=0;i+k<=n;i++){ //是从a[0]开始存储数组的,因此最后一个窗口的起点下标是n-k //如果是从a[1]开始存储的话,最后一个窗口的起点下标是n-k+1 maxx[i]=-1<<31,minn[i]=1<<31-1; for(int j=i;j<i+k;j++){ maxx[i]=max(maxx[i],a[j]); minn[i]=min(minn[i],a[j]); } } for(int i=0;i<n-k+1;i++){ cout<<minn[i]<<" "; } cout<<endl; for(int i=0;i<n-k+1;i++){ cout<<maxx[i]<<" "; } return 0; } 
     
    
  

(左右滑动查看完整代码)

评测结果如下:

数据结构之线性结构——单调队列与滑动窗口:算法优化技巧之一

由于数据较水,大部分测试点也能通过。想要AC的话,还得使用单调队列。

2、以样例数据为例,先展示单调队列求窗口最小值的算法流程:

(1)初始队列为{1,3,-1,-3,5,3,6,7},先将1入队,初始队列为{1};

(2)3>1,3入队,更新队列为{1,3};

(3)-1<3同时-1<1,因此3和1依次从队尾出队,-1入队,同时这是第1个窗口,输出队首-1,队列为{-1};

(4)-3<-1,因此-1从队尾出队,-3入队,输出队首-3,并更新队列为{-3};

(5)5>-3,入队,输出队首-3,并更新队列为{-3,5};

(6)3<5,5从队尾出队,3入队,输出队首-3,并更新队列为{-3,3};

(7)6>3,入队,更新队列为{-3,3,6},这里就出现问题了。

由于-3不在当前窗口中,因此需要-3从队首出队。但是,如何判断-3在不在窗口中呢?

在上面的流程中,都是直接将值作为队列元素入队,但是没有很好的办法来查找这个元素对应的数组下标。

因此,需要入队的不是元素的值,而是元素的下标,这样就方便判断元素是否在窗口中了。

结合上述的改进,再用一张表格展示完整的单调队列维护过程:

数据结构之线性结构——单调队列与滑动窗口:算法优化技巧之一

在本例题中还需注意,等第一个窗口的元素完全入队后,才能进行第一次输出。因此在判断队首是否在窗口内和输出前都要加上判断i>=m。

【参考代码】

#include 
  
    #include 
   
     using namespace std; int a[]; deque 
    
      q; int main(){ ios::sync_with_stdio(0); //加快cin、cout速度 int n,k; cin>>n>>k; for(int i=1;i<=n;i++){ cin>>a[i]; } for(int i=1;i<=n;i++){ whileq.empty aq.back>=a[i]){ //这里注意细节,如果队尾的值与当前值相等,也要让后来的入队 q.pop_back(); } q.push_back(i); //注意入队的是数组下标 if(i>=k){ //第一个窗口完全入队后再输出 while(q.front()<=i-k){ //为保险这里也可以加上判断:队列不为空 q.pop_front(); //如果队头不在窗口,队头出队 } cout<<a[q.front()]<<" "; } } cout<<'\n'; while(!q.empty()){ //清空deque,为求最大值做准备 q.pop_front(); } for(int i=1;i<=n;i++){ //滑动窗口找最大值,代码与上面镜像 while(!q.empty() && a[q.back()]<=a[i]){ q.pop_back q.push_backi ifi>=k){ while(q.front()<=i-k){ q.pop_front(); } cout<<a[q.front()]<<" "; } } cout<<'\n'; return 0; } 
     
    
  

(左右滑动查看完整代码)

评测结果如下:

数据结构之线性结构——单调队列与滑动窗口:算法优化技巧之一

与普通的算法相比,在时间复杂度和空间复杂度上都有显著的提升。

本题使用单调队列算法的时间复杂度为O(n),空间复杂度为O(n+k)。

例题2:

扫描

数据结构之线性结构——单调队列与滑动窗口:算法优化技巧之一

算法解析

根据题目的描述,很容易发现其为滑动窗口问题,并且是求滑动窗口最大值,可以使用单调队列求解。具体算法实现和例题1一致,因此不再赘述。

例题1中展示的代码是从a[1]开始赋值的数组,这里将展示从a[0]开始赋值数组的单调队列代码。读者们可以根据自己的编程习惯选择性掌握内容。

【参考代码】

#include 
  
    #include 
   
     using namespace std; int a[]; deque 
    
      q; int main(){ ios::sync_with_stdio(0); //加快cin、cout速度 int n,k; cin>>n>>k; for(int i=0;i 
     
       >a[i]; } for(int i=0;i<n;i++){ while(!q.empty() && a[q.back()]<=a[i]){ q.pop_back q.push_backi ifi>=k-1){ //这里是核心代码唯一的不同点 while(q.front()<=i-k){ //同样这里可以加上!q.empty() q.pop_front(); } cout<<a[q.front()]<<endl; } } return 0; } 
      
     
    
  

单调栈是始终保持栈内元素是单调递增或单调递减的栈,主要用于寻找左侧(或右侧)第一个比当前元素大(或小)的元素问题。

单调栈相较于单调队列要更为简单,因此读者们对本期的单调队列理解透彻的话,下期内容的掌握会更加简单。

如果对单调队列应用还不熟练的读者,可以在洛谷上尝试P1440、P2629、P2422、P2251等单调队列的经典题,巩固算法。

数据结构之线性结构——单调队列与滑动窗口:算法优化技巧之一

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/175929.html

(0)
上一篇 2025-04-10 07:20
下一篇 2025-04-10 07:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信