题目描述
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
每个窗口对应的最大值:
{[2,3,4],2,6,2,5,1}:4
{2,[3,4,2],6,2,5,1}:4
{2,3,[4,2,6],2,5,1}:6
{2,3,4,[2,6,2],5,1}:6
{2,3,4,2,[6,2,5],1}:6
{2,3,4,2,6,[2,5,1]}:5
方法一
求滑动窗口中的最大值,最直接的方法就是对于滑动窗口中的值进行一次遍历,记录最大值,将其放入返回结果vector<int> res中。
因为是滑动窗口,每个窗口相对于前一个窗口其实就是剔除了一个值,然后加入一个新的值,其余部分的值都是重复的,所以希望能够利用这个特性,减少比较操作。
很容易想到,当新加进来的值比之前的最大值temp_res要大的时候,新的滑动窗口的最大值就应该是这个新加进来的值;
但是当新加进来的值比之前的最大值temp_res要小的时候,新的滑动窗口的最大值却不一定仍然是前一个窗口的最大值,因为前一个窗口的最大值可能已经不在当前窗口中了,对于这种情况,当前一个窗口的最大值被移出时,没有想到好的办法来找到新的最大值,所以只能将当前窗口中的值进行遍历来寻找最大值。
代码如下:
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> res; // 返回结果
if( size > num.size() || num.size() < 0 || size <= 0 )
return res;
int temp_res = num[0];
for( int i=1;i<size;i++ ) // 寻找第一个窗口的最大值
temp_res = max( temp_res, num[i] );
res.push_back( temp_res );
for( int i=1,j=size;i<=num.size()-size;i++,j++ ) // 寻找剩余窗口的最大值
{
if( num[j] >= temp_res ) // 新加入的值比上一个窗口的最大值大
temp_res = num[j];
else // 新加入的值比上一个窗口的最大值小
{
temp_res = num[i];
for( int k=i;k<=j;k++ )
temp_res = max( temp_res, num[k] );
}
res.push_back( temp_res );
}
return res;
}
};
方法二
上述方法依然不够好,相对于直接对每个窗口进行遍历求最大值并没有大的改进,看了剑指offer的讲解,解决了当前一个窗口的最大值被移出时,如何找到新的最大值的问题。
可以采用一个双端队列dq,将有可能成为滑动窗口最大值的数值的下标存入dq(之所以存下标,是为了方便判断数值是否已经被移出滑动窗口)。希望做到的效果是:将最大值放在dq头部,其他可能成为最大值的数放在后面。
数组的第一个数字是2,把它存入dq;第二个数字是3>2,则2不可能成为最大值了,把2删除,将3加入dq;同理,第三个数4>3,将3删除,4加入dq。此时已经是一个滑动窗口,该滑动窗口的最大值为4,放在dq的头部。
接下来为2<4,当4被移出窗口的时候,2是有可能变为最大值,则把2放入队列的尾部。
第五个数6>4且6>2,则4和2已经不可能成为后续窗口的最大值,将4和2都删除,将6加入dq。
第六个数2<6,将其加入dq尾部。
第七个数5<6,5>2,因此2已经不可能是后续窗口最大值,将2删除,5放入dq。
最后一个数1<5,将其加入dq尾部。此时要注意6已经被移出窗口,那么6应该被删除。所以此时最大值为5。判断是否需要将队列头部的值删除:因为存入的是最大值对应的下标,所以可以根据下标判断。
那么总的流程大概如下:
- 首先处理好第一个窗口,将其最大值放入dq中;
- 如果新来的值比队列尾部的数小,那就追加到后面,因为它可能在前面的最大值划出窗口后成为最大值;
- 如果新来的值比尾部的大,那就删掉尾部(因为已经出现了更大的值,所以尾部数值已经不会成为最大值,所以删除),循环对新的队尾数值进行判断,直到队列为空或者队尾数值大于新来的值,再将新来的值放在队列尾部。(原本还对队列头部的值与新来的值进行了比较,但是发现前面的过程(从队尾最后一个值开始将队尾的值与新来的值进行比较,如果小于新来的值则将队尾元素删除,直到队列为空或者队尾数值大于新来的值),其实相当于已经将队列头部的值与新来的值进行了比较,所以就不需要再次比较了);
- 如果追加的值的索引i与队列头部的值的索引只差大于等于窗口大小,即i-dq.front() >= size,那就删掉头部的值。
代码如下
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> res; // 返回结果
if( size > num.size() || num.size() < 0 || size <= 0 )
return res;
deque<int> dq; // 双端队列存放可能成为最大值的值的下标
for( int i=0;i<size;i++ )
{
if( dq.empty() )
{
dq.push_back(i);
continue;
}
while( !dq.empty() && num[i] > num[dq.back()] ) // 循环对队尾元素与新值进行比较
dq.pop_back();
dq.push_back(i);
}
res.push_back(num[dq.front()]); // 第一个滑动窗口的最大值放入res
for( int i=size;i<num.size();i++ )
{
if( i-dq.front() >= size ) // 判断队首元素是否已经移除窗口
dq.pop_front();
while( !dq.empty() && num[i] > num[dq.back()] ) // 循环对队尾元素与新值进行比较
dq.pop_back();
dq.push_back(i);
res.push_back( num[dq.front()] ); // 将最大值放入res
}
return res;
}
};