[数据结构]手写循环队列解决滑动窗口问题

😀手写双端队列解决滑动窗口问题

问题:

滑动窗口(洛谷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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值