题目要求
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。
题目分析
使用蛮力法,先获得所有的滑动窗口,然后在得到窗口的最大值即可。但是这个方法的时间复杂度是O(k*n),和滑动窗口的大小和元素总个数有关。
所以我们想一个别的办法,争取用一次遍历就可以解决问题,我们用一个双头队列来存放每个滑动窗口的最大值,如果当前滑动到的元素num[i]比队列的尾部值大num[q.back()]大,那么替换队列尾部的值为当前滑动到的元素,并且保证最大值在队列首部,如果比他小那么直接加入到队列尾部。 如果当前队列中的最大值所在位置超过了滑动窗口大小的范围,那么要删除队列的最大值(即队首元素),保持这个算法,一直到遍历完全部元素即可。时间复杂度是O(n)
主要代码c++
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int>res; // 存放最后结果
deque<int>q; //存放滑动窗口最大值的下标
for(unsigned int i=0; i<num.size(); ++i)
{
while(q.size() && num[i]>num[q.back()]) //如果当前值比队列的最尾部值num[q.back()]大,那么删除队列尾部,保证最大值在队列首部
q.pop_back();
if(q.size() && i - q.front() + 1 >size) //如果最大值超过了size的区间(i-q.front()+1),那么就把最大值删除
q.pop_front();
q.push_back(i); //把每次滑动的下标加入队列
if(size && (1+i)>=size)
res.push_back(num[q.front()]); //存放数值
}
return res;
}
};
总结
实际上,一开始想通过一遍for和单纯的记录滑动窗口中的最大值来尝试解题,但是在做题的时候发现每次需要比较的数量随着滑动窗口的不同而不固定,而且两层for的架构使得复杂度太高,还没有得到更好的解决办法,所以后来参考评论区的解题思路得到了上述方法。