您的位置:首页 > 职场人生

《剑指Offer》学习笔记--面试题65:滑动窗口的最大值

2015-05-19 12:46 351 查看
题目:给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小,那么一共存在6个滑动窗口,它们的最大值分别为{4,4,6,6,6,5}。

如果采用蛮力法,这个问题似乎不难解决:可以扫描每一个滑动窗口的所有数字并找出其中的最大值。如果滑动窗口的大小为k,需要O(k)时间才能找出滑动窗口里的最大值。对于长度为n的输入数组,这个算法总的时间复杂度是O(nk)。

实际上一个滑动窗口可以看成是一个队列。当窗口滑动时,处于窗口的第一个数字被删除,同时在窗口的末尾添加一个新的数字。这符合队列的先进先出特性。如果能从队列中找出它的最大数,这个问题也就解决了。

在面试题21中。我们实现了一个可以用O(1)时间得到最小值的栈。同样,也可以用O(1)时间得到栈的最大值。同时在面试题7中,我们讨论了如何用两个栈实现一个队列。综合这两个问题的解决方法,我们发现如果把队列用两个栈实现,由于可以用O(1)时间得到栈中的最大值,那么也就可以用O(1)时间得到队列的最大值,因此总的时间复杂度也就降到了O(n)。

我们可以用这个方法来解决问题。不过这样就相当于在一轮面试的时间内要做两个面试题,时间未必够用。再来看看有没有其它的方法。

下面换一种思路。我们并不把滑动窗口的每个数值都存入队列中,而只把有可能成为滑动窗口最大值的数值存入到一个两端开口的队列。接着以输入数字{2,3,4,2,6,2,5,1}为例一步分析。

数组的第一个数字是2,把它存入队列中。第二个数字是3.由于它比前一个数字2大,因此2不可能成为滑动窗口中的最大值。2先从队列里删除,再把3存入到队列中。此时队列中只有一个数字3.针对第三个数字4的步骤类似,最终在队列中只剩下一个数字4.此时滑动窗口中已经有3个数字,而它的最大值4位于队列的头部。

接下来处理第四个数字2。2比队列中的数字4小。当4滑出窗口之后2还是有可能成为滑动窗口的最大值,因此把2存入队列的尾部。现在队列中有两个数字4和2,其中最大值4仍然位于队列的头部。

下一个数字是6.由于它比队列中已有的数字4和2都大,因此这时4和2已经不可能成为滑动窗口中的最大值。先把4和2从队列中删除,再把数字6存入队列。这个时候最大值6仍然位于队列的头部。

第六个数字是2.由于它比队列中已有的数字6小,所以2也存入队列的尾部。此时队列中有两个数字,其中最大值6位于队列的头部。

接下来的数字是5.在队列中已有的两个数字6和2里,2小于5,因此2不可能是一个滑动窗口的最大值,可以把它从队列的尾部删除。删除数字2之后,再把数字5存入队列。此时队列里剩下两个数字6和5,其中位于队列头部的是最大值6.

数组最后一个数字是1,把1存入队列的尾部。注意到位于队列头部的数字6是数组的第5个数字,此时的滑动窗口已经不包括这个数字了,因此应该把数字6从队列删除。那么怎么知道滑动窗口是否包括一个数字?应该在队列里存入数字在数组里的下标,而不是数值。当一个数字的下标与当前处理的数字的下标之差大于或者等于滑动窗口的大小时,这个数字已经从滑动窗口中滑出,可以从队列中删除了。

我们注意到滑动窗口的最大值总是位于队列的头部。可以用如下C++代码实现这个解决方案:

vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> maxInWindows;
if(num.size() >= size && size >= 1){
deque<int> index;
for(unsigned int i = 0; i < size; ++ i){
while(!index.empty() && num[i] >= num[index.back()])
index.pop_back();

index.push_back(i);
}

for(unsigned int i = size; i < num.size(); ++ i){
maxInWindows.push_back(num[index.front()]);

while(!index.empty() && num[i] >= num[index.back()])
index.pop_back();

if(!index.empty() && index.front() <= (int)(i - size))
index.pop_front();

index.push_back();
}
maxInWindows.push_back(num[index.front]);
}

return maxInWindows;
}
在上述代码中,index是一个两端开口的队列,用来保存有可能是滑动窗口最大值的数字下标。在存入一个数字的下标之前,首先要判断队列里已有数字是否小于待存入的数字。如果已有的数字小于待存入的数字,这些数字已经不肯能是滑动窗口的最大值,因此它们将会依次从队列的尾部删除。同时,如果,如果队列头部的数字已经从窗口里滑出,滑出的数字也需要从队列的头部删除。由于队列的头部和尾部都有可能删除数字,这也是需要两端开口的队列的原因。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: