第四节课:滑动窗口和单调栈


1.滑动窗口

初始时窗口的左右边界均在-1位置,只能往右移动而不能往左移动,且窗口左边界l不能超过右边界r

在这里插入图片描述
问题:对于每种窗口状态,求窗口内的最大值/最小值。可以对每个窗口进行遍历,求出最大值,但是时间复杂度较大
使用双端队列(C++中的deque)可以解决(以最大值为例):双端队列中存放的是数组的下标;需要保证双端队列中存储的下标对应的数组中元素是从大到小的(头部大 尾部小);r右移动,数据从尾部进入(push_back),如果不满足单调性的要求,则将数据依次从尾部弹出(需要保证严格的单调性,相等的数也要弹出),直到满足要求再将当前数据插入;窗口的最大值就是当前双端队列头部(front)的值;l右移动,检查出窗口的位置是否为双端队列头部存储的位置,如果是,则从头部弹出,否则,不进行操作

在这里插入图片描述
为什么每次双端队列头部维持的就是当前窗口的最大值呢?双端队列中维持的是:如果不让r移动,而只让l移动,谁会依次称为最大值,这个优先级信息
为什么弹出的数据可以不要了?因为数据弹出的条件是新进双端队列的数比前面的数大,这意味着新进来的数更大并且下标更晚过期,使用前面较小的数再也没可能成为最大值了,可以放心弹出
滑动窗口的平均代价:O(1),但可能某个点的代价会比较高
在这里插入图片描述

例:滑动窗口的最大值
在这里插入图片描述
代码实现:

vector<int> getMaxWindow(vector<int>& arr, int w) {
	if (arr.size() == 0 || w < 1 || arr.size() < w) {
		return vector<int>();
	}
	deque<int>dq;
	vector<int>res;
	for (int i = 0; i < arr.size(); i++) {
		while (!dq.empty() && arr[dq.back()] <= arr[i]) {
			dq.pop_back();
		}
		dq.push_back(i);
		if (dq.front() == i - w) {//下标过期
			dq.pop_front();
		}
		if (i >= w - 1) {//窗口形成
			res.push_back(arr[dq.front()]);
		}
	}
	return res;

}

2.单调栈

解决这样的问题:给定一个数组,找出每个元素左边和右边比这个数大/小的第一个数(离得最近),要求时间复杂度:O(N)
暴力方法:对于每个元素,分别向左右两边遍历(O(N^2))
在这里插入图片描述
先假定数组中没有重复值的情况,以找两边的数比当前值大的为例:维护一个从栈底到栈顶从大到小的单调栈,栈中存放的是数组的下标;如果不违反单调栈的单调性则直接将数放入栈中,否则将栈中的元素弹出,元素弹出时会生成想要的信息:左边离弹出值最大的就是栈中弹出值的下一个元素、右边离弹出值最大的就是当前数,直到不违反单调性/栈空,将当前值压入栈中;当数组中所有数据遍历完之后,如果栈中还有数据,依次弹出,左边比栈顶元素大的就是栈中下一个元素,右边比栈顶元素大的 无
在这里插入图片描述
为什么这样是对的?无重复值的情况:

在这里插入图片描述
在这里插入图片描述
代码实现:

//无重复数
vector<vector<int>> getNearMoreNoRepeat(vector<int>& arr) {
	if (arr.size() == 0) {
		return vector<vector<int>>();
	}
	vector<vector<int>>res(arr.size(), vector<int>(2));
	stack<int>sk;
	for (int i = 0; i < arr.size(); i++) {
		while (!sk.empty() && arr[sk.top()] < arr[i]) {
			int peek = sk.top();
			sk.pop();
			res[peek][0] = sk.empty() ? -1 : sk.top();//左
			res[peek][1] = i;//右
		}
		sk.push(i);
	}
	while (!sk.empty()) {
		int peek = sk.top();
		sk.pop();
		res[peek][0] = sk.empty() ? -1 : sk.top();
		res[peek][1] = -1;
	}
	return res;
}

右重复值的情况:
在这里插入图片描述
代码实现:

//有重复数
vector<vector<int>> getNearMoreRepeat(vector<int>& arr) {
	if (arr.size() == 0) {
		return vector<vector<int>>();
	}
	vector<vector<int>>res(arr.size(), vector<int>(2));
	stack<vector<int>>sk;
	for (int i = 0; i < arr.size(); i++) {
		while (!sk.empty() && arr[sk.top()[0]] < arr[i]) {
			vector<int> peek = sk.top();
			sk.pop();
			for (int index : peek) {
				res[index][0] = sk.empty() ? -1 : sk.top()[sk.top().size() - 1];
				//res[index][0] = sk.empty() ? -1 : sk.top()[0];
				res[index][1] = i;
			}
		}
		if (!sk.empty() && arr[sk.top()[0]] == arr[i]) {
			sk.top().push_back(i);
		}
		else {
			sk.push(vector<int>(1, i));
		}
	}
	while (!sk.empty()) {
		vector<int> peek = sk.top();
		sk.pop();
		for (int index : peek) {
			res[index][0] = sk.empty() ? -1 : sk.top()[sk.top().size() - 1];
			res[index][1] = -1;
		}
	}
	return res;
}

例:
在这里插入图片描述
补充条件:数组中的元素都是正数
假设当前元素作为子数组的最小值,求该子数组的指标A。以当前元素作为最小值的子数组,指标A最大的情况是:当前元素向两边找比当前元素大的第一个元素(单调栈)。遍历整个数组,求各个子数组的指标A,取最大
在这里插入图片描述
代码实现:

//有重复数
vector<vector<int>> getNearMoreRepeat(vector<int>& arr) {
	if (arr.size() == 0) {
		return vector<vector<int>>();
	}
	vector<vector<int>>res(arr.size(), vector<int>(2));
	stack<vector<int>>sk;
	for (int i = 0; i < arr.size(); i++) {
		while (!sk.empty() && arr[sk.top()[0]] > arr[i]) {
			vector<int> peek = sk.top();
			sk.pop();
			for (int index : peek) {
				res[index][0] = sk.empty() ? -1 : sk.top()[sk.top().size() - 1];
				res[index][1] = i;
			}
		}
		if (!sk.empty() && arr[sk.top()[0]] == arr[i]) {
			sk.top().push_back(i);
		}
		else {
			sk.push(vector<int>(1, i));
		}
	}
	while (!sk.empty()) {
		vector<int> peek = sk.top();
		sk.pop();
		for (int index : peek) {
			res[index][0] = sk.empty() ? -1 : sk.top()[sk.top().size() - 1];
			res[index][1] = -1;
		}
	}
	return res;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值