牛客网. 生成窗口最大值数组

题目概述

解题思路

这题要想暴力求解,其实就是每个滑窗求个最值,这样时间复杂度就是O(N * log(W))。这题看着应该有复杂度是O(N)的解法。

想想看该怎么做呢?通常的做法是:找到一个更贴近题目的规律 + 空间换时间的思路。

  • 首先思考一下,如何用空间换时间。一般来说,这种带有窗口的题目,维护一个窗口大小的数组就差不多了。这里我们不妨维护一个窗口大小的最大值数组。
  • 然后就是找规律。我们维护的数组显然与当前滑窗附近的这几个滑窗的最大值有关,那么随着滑窗的右移,最值的变化有怎样的规律呢?
    • 如果遇到的数字比现有的最大值更大,那么之前的最值就没有价值了(因为之后几个滑窗的最值不会小于这个值)
    • 如果遇到的数字比现有的最大值小,那就要考虑保留它,因为很可能当前的最大值马上就失效了。

从这个思路出发,我们维护一个存储窗口最大值的数组(队列)q_max。

  1. 首先,这个队列的尺寸不得超过窗口的大小,这样就能保证最大值的时效性。
  2. 然后,q_max的队首元素与当前滑窗的最大值相关,这样便于获取当前滑窗的最大值。
  3. 接着是更新队列的策略:
    1. 如果碰到的数组中新的元素小于当前队尾的元素,则将该元素放入队列;
    2. 如果该元素大于队尾的元素,则将队尾的元素丢弃,直至该元素大于队尾的元素或者队列中无元素。
  4. 考虑到我们需要确定队首元素的时效性,也就是比较队首元素与当前滑窗和窗口宽度的位置关系,因此不妨在队列里存储元素的序号。

小知识:这里我们采用了STL的双端队列deque (double ended queue的缩写,头文件是deque.h),这个数据结构的特点是便于在头部和尾部插入元素。

本题用到的数据结构类型也被称作“单调队列”,它能动态地维护定长序列中的最值,可应用于求最值与优化DP转移等方面。

原理

单调队列实质上是一类双端队列(deque),在加入一个元素时,我们通过一定操作维护队列的单调性。该队列特征的操作便是“后移一位”。顾名思义,后移一位就是指队列维护的区间往后移一位。这个操作中要做的事情是弹出队首与加入队尾。

加入队尾

为了保证这个队列中元素的单调性,对于加入的元素,我们可能要删除一些队尾的元素。以不下降的单调队列为例(下同),插入时要弹出所有比要插入元素大的队尾元素,直到队尾元素不再比其大再插入。此时插入元素时要记下插入元素在原序列中的位置,这个信息将会对弹出队首时发挥作用。

弹出队首

对于插入的元素,我们检查它与队首元素的位置之差,当这个差大于队列定长时说明队首元素已在区间之外,应当弹出。

——单调队列原理及其应用

算法性能

这个方法的空间复杂度是O(N+W);

时间复杂度是滑窗的次数O(N) + 元素进出队列的次数O(N * 2),所以也是O(N)。

示例代码

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<cstring>
#include<queue>
#include<deque>
#include<iomanip>

using namespace std;

int main()
{
	int list_len, win_len, temp_max = 0;
	int data[1000001];
	deque<int> qmax;
	cin >> list_len >> win_len;
	for (int di = 0; di < list_len; ++di)
		scanf("%d", data + di);

	for (int di = 0; di < list_len; ++di)
	{
		if (qmax.empty())
			qmax.push_back(di);
		else
		{
			if (data[di] <= data[qmax.back()])
				qmax.push_back(di);
			else
			{
				while (!qmax.empty() && (data[di] > data[qmax.back()]))
					qmax.pop_back();
				qmax.push_back(di);
			}
		}

		temp_max = qmax.front();
		if ((di - win_len) >= temp_max)
			qmax.pop_front();

		if (di + 1 >= win_len)
			cout << data[qmax.front()] << " ";
	}

	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值