和单调栈一样维持一个单调性的队列,这个队列中的数比实际数组的数要少,因为删去了不必要的部分。
我遇到的题一般会把单调队列作为一个辅助数据结构来存储当前状态。以递减的单调队列为例子,它的特点就是每次都能取到队首最大的元素,这也就是我们当前状态的最大元素。
以例题来看具体过程。
剑指offer59 队列最大值
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
思路:我们除了要知道当前最大元素,还要知道这个最大元素出队后下一个元素是谁,因此我们用单调队列来记录当前队列里的最大元素情况,队首是最大,队首出队后,下一个就是新的最大。
队列操作有两个,入队和出队,我们分别来看。
入队元素a
如果a比单调队列队尾的元素e大,说明当队列前面大的元素出队后,下一个最大的该是a而不该是e,所以e无需要保留,把e移出队伍。
出队元素b
如果出队的元素和我们的单调队列 front元素一致,那表明这个最大值出队了,我们也要把front移出单调队列。如果和front不一致,说明最大的元素还在队列里,此时front不变,因此单调队列无变化。
class MaxQueue {
private:
queue<int> main_que;
deque<int> max_values;
public:
MaxQueue() {
}
int max_value() {
if(max_values.empty())
return -1;
return max_values.front();
}
void push_back(int value) {
main_que.push(value); //主队列直接进
while(!max_values.empty() && value > max_values.back()) //辅助单调队列去掉队尾比它小的
max_values.pop_back();
max_values.push_back(value);
}
int pop_front() {
if(main_que.empty())
return -1;
int val = main_que.front();
main_que.pop();
if(val == max_values.front()){ //最大值已被拿出
max_values.pop_front();
}
return val;
}
};
例题2:滑动窗口最大值
这里当前状态就是这个窗口,所以我们还是单调队列记录窗口最大值,单调递减。
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int>res;
deque<int>max_que;
if(k==0)
return res;
for(int i=0;i<k;i++){
while(!max_que.empty() && nums[i]>max_que.back() ){
max_que.pop_back();
}
max_que.push_back(nums[i]);
}
res.push_back(max_que.front());
for(int i=k;i<nums.size();i++){
//每次i进来,i-k出去
if(nums[i-k] == max_que.front()){
max_que.pop_front();
}
while(!max_que.empty() && nums[i]>max_que.back() ){
max_que.pop_back();
}
max_que.push_back(nums[i]);
res.push_back(max_que.front());
}
return res;
}
最后,和单调栈的区别是什么:栈里面我们关心的是对一个元素俩说它下一个比它大or前一个比它大的元素。队里面主要是维持当前状态队首最值,以及状态的更新通过出队入队就可以更新。