题目一:滑动窗口的最大值
给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值。例如输入数组[2,3,4,2,6,2,5,1]及滑动窗口大小3,那么一共存在6个滑动窗口,它们的最大值分别为[4,4,6,6,6,5]。
如果采用暴力解法,这个问题似乎不难解决,可以扫描滑动窗口里的每个数字找出其中的最大值。如果滑动窗口大小为k,则需要O(k)的时间可以找到滑动窗口里的最大值,对于长度为n的数组,这种算法的总时间复杂度为O(nk)。
我们可以维护一个双端队列,将每个滑动窗口中的最大值或者可能成为滑动窗口中最大值的数字维护在队列当中。
数组的第一个数字是2,把它先存入队列,第二个数字是3,3大于2,2就不可能是滑动窗口里的最大值。删除2,存入3。第三个数字4同理。删除3存入4,第四个数字2,2比4小 但当4滑出窗口时2,有可能成为滑动从窗口的最大值,因此将2存入队尾。第五个数字6他比4和2都大,这两个不可能是滑动窗口里的最大值了,将4和2从队列里删除,放入6。第六个数字2,存入尾部。 第七个数字5,5比2大,2不可能是滑动窗口里的最大值,删除2,存入5 。最后一个数字是1,1存入队列尾部,队列中的6是第五个数字,已经不在滑动窗口内了,应从队列中删除。那么怎么知道当前的数字是否为滑动窗口内的数字呢 ?我们在队列内存放数字的下标,当一个数字的下标与当前处理的数字的下标差大于等于滑动窗口的大小时,他已经从窗口中滑出,可以从队列中删除了。
步骤 | 数字 | 队列内元素(下标对应的数字) | 最大值 |
---|---|---|---|
1 | 2 | 0(2) | \ |
2 | 3 | 1(3) | \ |
3 | 4 | 2(4) | 4 |
4 | 2 | 2 (4)、3(2) | 4 |
5 | 6 | 4(6) | 6 |
6 | 2 | 4(6)、5(2) | 6 |
7 | 5 | 4(6)、6(5) | 6 |
8 | 1 | 6(5)、7(1) | 5 |
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int>ans;
if(k > nums.size() || nums.size()<= 0) return ans;
deque<int>index;
for(int i = 0;i < k;i++){
while(!index.empty() && nums[i] >= nums[index.back()]){
index.pop_back();
}
index.push_back(i);
}
ans.push_back(nums[index.front()]);
for(int i = k;i < nums.size();i++){
while(!index.empty() && nums[i] >= nums[index.back()]){
index.pop_back();
}
if(!index.empty() && (i-k) >= index.front())
index.pop_front();
index.push_back(i);
ans.push_back(nums[index.front()]);
}
return ans;
}
上面这种算法的时间复杂度为O(n)。
题目二:队列中的最大值
请定义一个队列并实现函数max得到队列里的最大值,要求max、push_back和pop_front的时间复杂度都为O(1)。
我们在做上面的题时就使用双端队列来存储了滑动窗口里的最大值,这个题的本质与上面的题一样,我们仍然使用双端队列来存储队列里的最大值。只是把数据从最大值队列里移除的条件除了它比要push进去的值小以外,还有就是如果它在正常的队列里被弹出了,也应该同步从最大值队列里删除。
class MaxQueue {
private:
queue<int>data;
deque<int>max;
public:
MaxQueue() {
}
int max_value() {
if(data.empty())
return -1;
else
return max.front();
}
void push_back(int value) {
while(!max.empty() && value >= max.back())
max.pop_back();
max.push_back(value);
data.push(value);
}
int pop_front() {
if(data.empty())
return -1;
int val = data.front();
data.pop();
if(max.front() == val)
max.pop_front();
return val;
}
};