写单调队列时,其实一共有三个操作:取队头,弹出过期元素,加入新元素; 要结合实际循环和题目来决定在循环体内部这三个操作的顺序,顺序,顺序!有时感觉不恰当的时候,往往是顺序没设置完美 --编辑于2020.06.27
题目描述:
有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 求在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少. 例如:
数列是 [1 3 -1 -3 5 3 6 7], 其中 k 等于 3.
思路:
求最小值时,维护一个单调递增队列,基本操作:
①元素进队操作:如果当前元素大于队尾则进队,否则弹出队尾,直到队空或者队尾元素小于当前元素;
②先把前k个元素都进队;
③从第k+1个元素到第n个元素遍历:遍历第i个元素时,当前队首的元素一定是第i-k个滑动窗口中的最小值;然后把过期的元素从队首删除,把第i个元素进队;遍历完n个元素之后,最后队首的元素是最后一个滑动窗口的最小值。
同理可以用单调递减队列求最大值。
正确性:
单调递增队列中的队首元素一定是最小元素
代码:
值得注意的是,用G++交超时,用C++交能过,原因不想追究,因为我一直在寻找问题,直到题面更新了尝试G++和C++...
STL的deque,支持队首和队尾都删除。
1 #include <cstdio> 2 #include <iostream> 3 #include <queue> 4 #include <deque> 5 using namespace std; 6 const int MAXN=1e6+5; 7 deque<int> Q; 8 int a[MAXN],maxi[MAXN],mini[MAXN]; //maxi[j]=k,第j个滑动窗口的最大值是k 9 int n,k; 10 int main() 11 { 12 cin>>n>>k; 13 for(int i=1;i<=n;i++) 14 scanf("%d",a+i); 15 for(int i=1;i<=k;i++) 16 { 17 while( !Q.empty() && a[i]< a[ Q.back() ] ) 18 Q.pop_back(); 19 Q.push_back(i); 20 } 21 for(int i=k+1;i<=n;i++) 22 { 23 mini[i-k]=a[ Q.front() ]; //第i-k个滑动窗口的最小值就是队头 24 while( !Q.empty() && i-Q.front()>=k ) 25 Q.pop_front(); 26 while( !Q.empty() && a[i]< a[ Q.back() ] ) 27 Q.pop_back(); 28 Q.push_back(i); 29 } 30 mini[n+1-k]=a[ Q.front() ]; 31 Q.clear(); 32 33 for(int i=1;i<=k;i++) 34 { 35 while( !Q.empty() && a[i]>a[ Q.back() ] ) 36 Q.pop_back(); 37 Q.push_back(i); 38 } 39 for(int i=k+1;i<=n;i++) 40 { 41 maxi[i-k]=a[ Q.front() ]; //第i-k个滑动窗口的最大值就是队头,但是i-k可能小于0,加了一个偏移MAXN 42 while( !Q.empty() && i-Q.front()>=k ) 43 Q.pop_front(); 44 while( !Q.empty() && a[i]>a[Q.back()] ) 45 Q.pop_back(); 46 Q.push_back(i); 47 } 48 maxi[n+1-k]=a[ Q.front() ]; 49 50 int t2=n-k+1; 51 printf("%d",mini[1]); 52 for(int i=2;i<=t2;i++) 53 printf(" %d" , mini[i] ); 54 printf(" %d",maxi[1]); 55 for(int i=2;i<=t2;i++) 56 printf( " %d" , maxi[i] ); 57 return 0; 58 }