窗口数组最大值及其应用

一、窗口数组最大值原型: 

描述:窗口大小为w,数组长度为n,求每个窗口数组的最大值,如下题返回一个数组为 [5 5 5 4 6 7]

流程: 准备一个双端队列,存放窗口数组最大值的下标准,双端队列保证从左往右递增,不包括等于,

1)窗口加数(R往右边移动):考察双端队列尾部和数组当前数的关系,尾部数大,直接从队尾入队,否则弹出,直到遇到尾部大于当前数的才将该数从队尾入队。

2)窗口减数(L往有边移动):考察双端队列的首部元素是否过期,过期则弹出即可,否则不弹出。

这里保证双端队列从大到小,相等也不行,因为相等不跟新的话最大值可能是前一个窗口留下来的。 

前面为了方便理解在双端队列中存放的数窗口的最大值,最后的方法就是存放的数原数组的下标,这样既可可以通过索引得到值,又可以得到该值在原数组的位置。

实现代码:

#include<iostream>
#include<vector>
#include<deque>
using namespace std;

vector<int> getMaxValueWindow(int arr[], int arr_len, int w) {
	vector<int> res;
	if (arr == NULL || w < 1 || arr_len < w) {
		return res;
	}
	deque<int> dq;
	int L = 0;//窗口左边界
	int R = 0;//窗口右边界
	for (int i = 0; i < arr_len; ++i) {
		while (!dq.empty() && arr[dq.back()] <= arr[i]) {//只要队列不为空,并且比进来的数要比队尾的小,则弹出
			dq.pop_back();
		}
		dq.push_back(i);
		if (R - L + 1 != w)//还没到窗口,右边R移动
			R++;
		else {//到了窗口
			res.push_back(dq.front());//取出第一个窗口最大数组放入数组中
			if (dq.front() == L) {//判断窗口左边界是否到期,到期直接弹出
				dq.pop_front();
			}
			L++; 
			R++;
		}
	}
	return res;
}
int main() {
	int arr[] = { 5,4,1,2,6,0,5,1,6,9,10,23 };
	int w;
	while (cin >> w) {
		vector<int> res = getMaxValueWindow(arr, 12, w);
		for (int i = 0; i < res.size(); ++i) {
			cout << arr[res[i]] << ' ';
		}
		cout << endl;
	}
}

二、应用:

 

方法一:暴力解,两个for循环搞出所有子数组,然后用一个函数判断该子数组是否满足情况即可:

1)for循环遍历,得到子数组的下标: 

int getNum1(int arr[], int arr_len, int num) {
	int res = 0;
	for (int start = 0; start < arr_len; start++) {//通过两个for循环搞出所有子数组,然后用一个isValid函数判断数组是否有效
		for (int end = start; end < arr_len; end++) {
			if (isValid(arr, start, end, num)) {
				res++;
			}
		}
	}
	return res;
}

 2)isVlid函数判断子数组是否满足条件:

bool isValid(int arr[],int start, int end,int num) {
	int MAX = INT_MIN;
	int MIN = INT_MAX;
	for (int i = start; i < end; ++i) {//求出子数组的最大值和最小值
		MAX = MAX > arr[i] ? MAX : arr[i];
		MIN = MIN < arr[i] ? MIN : arr[i];
	}
	return MAX - MIN <= num;
}

上述那个for两个循环O(N^2)然后isValid又是O(N),所以是O(N^3)...

方法二:O(N)的时间复杂度

 思路:

1)如果一个窗口数组满足max-min<=num,则其内部所有子数组都满足max-min<=num。因为其子数组中的sub_max

只能比max小,sub_min只能比min大,之差所以一定满足<=num.

2)如果一个窗口数组满足max-min<=num,再增加一个元素就不达标了,那么窗口无论继续增加多少元素都不达标,因为继续扩只能让max变大min变小的可能性增加。

3)理解了1和2思路就会变得异常简单。让L停留在0,R从0往右边扩,直到遇到一个不达标的停止,则该窗口内所有子数组都达标,统计达标的子数组个数;然后L前进一步,窗口内有可能就会达标了,所以此时让R继续扩,就有L每前进一步R就往右走。这样准备两个双端队列,一个存放窗口最大数组,一个存放窗口最小数组即可。

测试代码: 

#include<iostream>
#include<vector>
#include<deque>
using namespace std;

bool isValid(int arr[], int start, int end, int num);

//暴力搜索
int getNum1(int arr[], int arr_len, int num) {
	int res = 0;
	for (int start = 0; start < arr_len; start++) {//通过两个for循环搞出所有子数组,然后用一个isValid函数判断数组是否有效
		for (int end = start; end < arr_len; end++) {
			if (isValid(arr, start, end, num)) {
				res++;
			}
		}
	}
	return res;
}
bool isValid(int arr[], int start, int end, int num) {
	int MAX = INT_MIN;
	int MIN = INT_MAX;
	for (int i = start; i <= end; ++i) {
		MAX = MAX > arr[i] ? MAX : arr[i];
		MIN = MIN < arr[i] ? MIN : arr[i];
	}
	return MAX - MIN <= num;
}


//窗口数组最大值方法
int getNum2(int arr[], int arr_len,int num) {
	int res = 0;
	if (arr == NULL) {
		return res;
	}
	deque<int> dq_max;
	deque<int> dq_min;
	int L = 0;//窗口左边界
	int R = 0;//窗口右边界
	while (L < arr_len) {
		while (R < arr_len) {
			//窗口数组的最大值
			while (!dq_max.empty() && arr[dq_max.back()] <= arr[R]) {
				dq_max.pop_back();
			}
			dq_max.push_back(R);
			//窗口数组的最小值
			while (!dq_min.empty() && arr[dq_min.back()] >= arr[R]) {
				dq_min.pop_back();
			}
			dq_min.push_back(R);
			if (arr[dq_max.front()] - arr[dq_min.front()] > num) {//不达标直接break,后面的都不用考虑了
				break;//如果不到R小于arr_len berak之后还会进来该循环
			}
			R++;
		}
		if (dq_max.front() == L) {//判断窗口左边界是否到期,到期直接弹出,然后L++
			dq_max.pop_front();
		}
		if (dq_min.front() == L) {//判断窗口左边界是否到期,到期直接弹出,然后L++
			dq_min.pop_front();
		}
		res += R - L;//这个边界需要注意,是R-L而不是R-L+1是因为R在循环中已经到下一个位置了。
	    //如:0-3本来有子数组:0-0,0-1,0-2,0-3 就是3-0+1=4个,但是现在在while中R已经来到了4的位置,所以就是直接R-L.
		L++; 
	}
	return res;
}
int main() {
	int arr[] = {5,4,7,8,6,3,1,10,9,11};
	int w;
	while (cin >> w) {
		int res1 = getNum1(arr, 10, w);
		cout << res1 << endl;
		int res2 = getNum2(arr, 10, w);
		cout <<res2 << endl;
	}
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无情的搬砖机器

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值