😀手写双端队列解决滑动窗口问题
问题:
滑动窗口(洛谷1886)
问题简述:有长度为n的序列a,及大小为m的窗口。从左边向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最小值和最大值,例如下图。
测试输入:
8 3
1 3 -1 -3 5 3 6 7
输出:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
问题分析
手写双端队列
本来想通过head和rear两个指针实现双端队列,但是写完后发现队头和队尾的插入始终存在很大问题。参考网友代码后发现,队列中使用head和size计数,用(head+size-1)%N表示rear更为简便。
滑动窗口问题
滑动窗口需要比较出队列中指定m个数中最小、最大值。这里采用的思路是用两个循环,分别比较出最大和最小值,每个元素最多入队1次、出队1次,所以时间复杂度为O(n)。以输出最小值为例,简述思路:
1.用数组a存储所有需要判断的n个数据;用双端队列Q作为滑动窗口,存储m个数据在数组a中的位置(可能是1,2,3…n)
2.保证滑动窗口内有m个数时,输出最小值
3.队首删除Q中,比下一个进入窗口的数字大的数在a中对应的序号;将下一个数字对应的序号存入Q的队尾
代码
//手写双端队列--滑动窗口
#include<iostream>
using namespace std;
#define N 1003
int a[N];
//创建双端队列及方法
struct myqueue {
//动态分配
int *data;
//通过head和size计算出尾rear的位置
int head, size;
//初始化
bool init()
{
Q.data = (int*)malloc(N* sizeof(int));
if (!Q.data) return false;
head = size = 0;
return true;
}
//front 返回队头
int front()
{
//判断是否为空
if (size==0)
{
return -1;
}
return data[head];
}
//back 返回队尾
int back()
{
//判断是否为空
if (size==0)
{
return -1;
}
int rear = (head + size - 1 + N) % N;
return data[rear];
}
//pop_back 删除队尾
bool pop_back()
{
//判断队列是否为空
if (size==0)
return false;
size--;
return true;
}
//pop_front 删除队头
bool pop_front()
{
if (size==0)
return false;
head = (head + 1) % N;
size--;
return true;
}
//push_back(e) 从队尾添加一个元素e
bool push_back(int e)
{
//判断队列是否已满
if (size==N)
{
return false;
}
size++;
int rear = (head + size-1)%N;
data[rear] = e;
return true;
}
//push_front(e) 在队头添加一个元素e
bool push_front(int e)
{
//判断队列是否已满
if (size==N)
{
return false;
}
size++;
head = (head - 1+N) % N;
data[head] = e;
return true;
}
bool empty()//判断是否为空
{
if (size==0)
return true;
return false;
}
}Q;
int main()
{
int n, m;//n个数,m为窗口大小
cin >> n >> m;
Q.init();
//读取n个数
for (int i = 1; i <=n; i++)
{
cin >> a[i];
}
//输出最小值
for (int i = 1; i <=n; i++)
{
//Q不为空,队尾元素大于新的元素,在队列中删除该元素
while (!Q.empty() && a[Q.back()] > a[i])
{
//cout << "尾";
//cout << Q.back() << endl;
Q.pop_back();
}
//添加新元素
Q.push_back(i);
//cout << "i:" << i << endl;
//如果Q超过滑动窗口大小,删除头
if(i>=m)
{
//保证滑动窗口中仅有m个数
while(!Q.empty() && Q.front() <= i - m)
{
//cout << "头";
//cout << Q.front() << endl;
Q.pop_front();//删去头
}
cout << a[Q.front()] << " ";
}
}
cout << endl;
//输出最大值
Q.init();//初始化队列,记录窗口对应的数字在数组中的序号
for (int i = 1; i <=n; i++)
{
//cout << "i=" << i << endl;
//if(i>1)
//cout << "新数" << a[i] << "老的:" << a[Q.back()] << endl;
//判断队列中是否有数,且是否全部大于要进来的数
while (!Q.empty()&&a[Q.back()]<a[i])
{
//要进来的数大,删除原来的数
//cout << "要进来的数为" << a[i] << endl;
//cout << "删除" << a[Q.back()] << endl;
Q.pop_back();
}
//引入新的i
Q.push_back(i);
//判断窗口是否有三个数
if(i>=m)
{
//cout << "i是"<<i<<"超过m了" << endl;
//滑动窗口是否<=m个数
while (!Q.empty()&&Q.front()<=i-m)
{
//不是,删除前面的数
//cout << "数字多了,删除" << Q.front() << endl;
Q.pop_front();
}
//输出
//cout << a[Q.front()] << " "<<endl;
cout << a[Q.front()] << " ";
}
}
return 0;
}
Tips:
There is always an easy solution to every problem——neat,plausible and wrong.
参考链接:
https://www.jianshu.com/p/b414c3177778