单调栈和单调队列 单调栈和优先队列 单调栈能保证全部元素的单调性,会直接舍去不合的元素,因此没有额外维护成本 小根堆只能保证堆顶是最小值,不会直接舍去元素,但需要O(logn)的decreaseKey成本
有些题目答案一定是当前最大最小值,直接用单调栈维护即可,不是答案直接舍去,时间复杂度为O(n) 当然使用优先队列也正确,因为堆顶一定是极值,但时间复杂度为O(nlogn)
另外,单调队列是双端版的单调栈,元素可以从两端排除,不等于优先队列
先想暴力怎么做,再考虑把没有用的元素删掉,再看有没有单调性,有单调性的话再看怎么优化
直接看逆序有没有用,若逆序没用,就有单调性!(在一个从小到大排列的数列中,若左边的数比右边的数大,就称作逆序数)
单调栈
一段本不具有单调性的区间,用一个栈去维护使得其具有单调性(将元素入栈,如果其是逆序的,就让它出栈)
步骤:
while循环 判断栈顶元素是否大于 a[i],如果大于则出栈
如果此时栈不空 则将新的元素 a[i] 赋给答案数组ans
如果此时栈空 则将 -1 赋给答案数组ans
将当前元素 a[i] 入栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <algorithm> using namespace std;const int N = 1e5 +10 ;int n,a[N];int stk[N],tt; int ans[N];int main () { cin >> n; for (int i=0 ;i<n;i++){ cin >> a[i]; } for (int i=0 ;i<n;i++){ while (tt && stk[tt] >= a[i]) tt--; if (tt) ans[i] = stk[tt]; else ans[i] = -1 ; stk[++tt] = a[i]; } for (int i=0 ;i<n;i++){ cout << ans[i] << " " ; } return 0 ; }
单调队列 题目链接:滑动窗口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std;const int N = 1e6 + 10 ;int n,k,a[N];int q[N],hh=0 ,tt=-1 ;int main () { cin >> n >> k; for (int i=0 ;i<n;i++){ cin >> a[i]; } for (int i=0 ;i<n;i++){ if (hh <= tt && i-k+1 > q[hh]) hh++; while (hh <= tt && a[q[tt]] >= a[i]) tt--; q[++tt] = i; if (i>=k-1 ) cout << a[q[hh]] << " " ; } cout << "\n" ; hh = 0 ; tt=-1 ; for (int i=0 ;i<n;i++){ if (hh <= tt && i-k+1 > q[hh]) hh++; while (hh <= tt && a[q[tt]] <= a[i]) tt--; q[++tt] = i; if (i>=k-1 ) cout << a[q[hh]] << " " ; } return 0 ; }
滑动窗口(双端队列) 维护一个固定长度窗口内的最大最小值
tips
要先判断 //要先判断 q.size() > 0
1 2 3 4 while (q.size () > 0 && a[i] < q.back ()){ q.pop_back (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> #include <deque> using namespace std;const int N = 1e6 +10 ;int n,k;int a[N];int main () { cin >> n >> k; for (int i=1 ;i<=n;i++){ cin >> a[i]; } deque<int > q; for (int i=1 ;i<=n;i++){ while (q.size () && q.back () > a[i]) q.pop_back (); q.push_back (a[i]); if (i-k >= 1 && q.front () == a[i-k]){ q.pop_front (); } if (i >= k){ cout << q.front () << " " ; } } cout << "\n" ; q.clear (); for (int i = 1 ; i <= n; i++) { while (q.size () && q.back () < a[i]) q.pop_back (); q.push_back (a[i]); if (i - k >= 1 && a[i - k] == q.front ()) q.pop_front (); if (i >= k) cout << q.front () << " " ; } return 0 ; }
滑动窗口的最大值问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <deque> #include <vector> #include <iostream> void printMaxInWindows (const std::vector<int >& nums, int windowSize) { std::deque<int > window; for (int i = 0 ; i < nums.size (); ++i) { while (!window.empty () && window.front () <= i - windowSize) { window.pop_front (); } while (!window.empty () && nums[window.back ()] < nums[i]) { window.pop_back (); } window.push_back (i); if (i >= windowSize - 1 ) { std::cout << nums[window.front ()] << " " ; } } }int main () { std::vector<int > nums = {4 , 3 , 5 , 4 , 3 , 3 , 6 , 7 }; int windowSize = 3 ; printMaxInWindows (nums, windowSize); return 0 ; }
完美区间 给出一个长度为N(1 ≤ N ≤ 50000)的数字队列,定义完美区间为数字队列中连续的一段,并且其中最大与最小数字之差不得超过M(1 ≤ M ≤ 50000)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <iostream> #include <vector> #include <deque> #include <algorithm> using namespace std;int countPerfectIntervals (vector<int >& nums, int M) { int n = nums.size (); int count = 0 ; deque<int > minDeque; deque<int > maxDeque; for (int i = 0 ; i < n; ++i) { while (!minDeque.empty () && nums[i] < nums[minDeque.back ()]) { minDeque.pop_back (); } minDeque.push_back (i); while (!maxDeque.empty () && nums[i] > nums[maxDeque.back ()]) { maxDeque.pop_back (); } maxDeque.push_back (i); while (nums[i] - nums[minDeque.front ()] > M) { if (minDeque.front () == i - minDeque.size () + 1 ) { minDeque.pop_front (); } } while (nums[maxDeque.front ()] - nums[i] > M) { if (maxDeque.front () == i - maxDeque.size () + 1 ) { maxDeque.pop_front (); } } count += (i - minDeque.front () + 1 ); } return count; }int main () { vector<int > nums = {1 , 5 , 3 , 9 , 2 , 8 , 7 , 4 , 6 }; int M = 3 ; cout << "Number of perfect intervals: " << countPerfectIntervals (nums, M) << endl; return 0 ; }
每个字母恰好出现两次的最长子串 给一个只包含小写字母且长度不超过100000的字符串,找出每个字母恰好出现两次的最长子串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <string> #include <unordered_map> #include <algorithm> using namespace std;string longestSubstringWithEqualFrequency (string s) { int n = s.length (); unordered_map<char , int > count; int maxLen = 0 ; int left = 0 ; string result; for (int right = 0 ; right < n; ++right) { char c = s[right]; count[c]++; while (count[c] > 2 ) { char leftChar = s[left]; count[leftChar]--; left++; } if (all_of (count.begin (), count.end (), [](const pair<char , int >& p) { return p.second == 2 ; })) { if (right - left + 1 > maxLen) { maxLen = right - left + 1 ; result = s.substr (left, maxLen); } } } return result; }int main () { string s = "aabbcc" ; cout << "Longest substring with equal frequency: " << longestSubstringWithEqualFrequency (s) << endl; return 0 ; }