原题链接//https://ac.nowcoder.com/acm/problem/51001
题意:给定一个数组,给你一个长度为k的窗口,窗口每次向右滑动一个位置,求每次滑动后窗口里最大/最小的数。
示例:
思路1:优先队列
最大值:维护一个长度为k的优先队列(pair类型,pair类型的优先队列排序是先按照first排序,再按照second序),那我们就可以定义一个priority_queue<pair<a[i], i> >, 当窗口移动后我们判断队列的头元素的second是否还在窗口中(移动窗口后,以前的元素可能已不再窗口内), 若在就将新元素加入到队列中,若不在就先将队首出列,然后再将新元素加入到队列中。这样每次输出队列的首元素的first即可。
最小值:同理建一个小顶堆,priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >,可求出每次窗口移动的最小值。
时间复杂度:从前往后遍历每个元素,复杂度为O(n),元素入队的复杂度为O(logn)(堆的操作为O(logn)),总复杂度为O(nlogn)。
代码:
priority_queue<pair<int,int> > pq1; //大顶堆
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > pq2; //小顶堆
vector<int> v1, v2; //存储最大值 最小值
void Sliding_Window(int a[], int n, int k)
{
for(int i=1;i<=n;i++)
{
while(!pq2.empty() && (pq2.top().second)<=i-k) //队首不在窗口内 出队
pq2.pop();
pq2.push(pair<int,int>(a[i],i)); //入队
while(!pq1.empty() && (pq1.top().second)<=i-k)
pq1.pop();
pq1.push(pair<int,int>(a[i],i));
if(i>=k)
{
v2.push_back(pq2.top().first);
v1.push_back(pq1.top().first);
}
}
}
使用优先队列的时间复杂度为O(nlogn),那是否有跟优秀的算法呢,答案是有的,接下来我们来说一下由双端队列解决该问题。
思路2:双端队列
双端队列的优点在与既可以从队首删除,也可以从队尾删除元素。
最大值:我们定义一个双端队列,当队列不空时,每次窗口移动后,将新元素与队列中的元素从后往前比较,将队列中比新元素小的元素删除(只要队列中的元素还在窗口内,且比新元素小,则窗口中最大值必不可能为队列中的元素),在此操作之前同样要将窗口移动后队列中已不在窗口的元素出列,那么每次操作之后队列的首元素既是窗口中的最大值。
最小值:同理将队列中比新元素大的全部删除即可。
时间复杂度:每个元素出队入队一次,出队一次,所以复杂度为O(n)。
代码:
deque<pair<int,int> > pq1;
deque<pair<int,int> > pq2;
vector<int> v1, v2;
void Sliding_Window(int a[], int n, int k)
{
for(int i=1;i<=n;i++)
{
while(!pq2.empty() && (pq2.front().second)<=i-k) //将不在窗口的元素出队
pq2.pop_front();
while(!pq2.empty() && (pq2.back().first>a[i])) //将比新元素大的元素出队
pq2.pop_back();
pq2.push_back(pair<int,int>(a[i],i));
while(!pq1.empty() && (pq1.front().second)<=i-k)
pq1.pop_front();
while(!pq1.empty() && (pq1.back().first<a[i]))
pq1.pop_back();
pq1.push_back(pair<int,int>(a[i],i));
if(i>=k)
{
v2.push_back(pq2.front().first);
v1.push_back(pq1.front().first);
}
}
}