题目一:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。
* 思路:滑动窗口应当是队列,但为了得到滑动窗口的最大值,队列序可以从两端删除元素,因此使用双端队列。
* 原则:
* 对新来的元素k,将其与双端队列中的元素相比较
* 1)前面比k小的,直接移出队列(因为不再可能成为后面滑动窗口的最大值了!),
* 2)前面比k大的X,比较两者下标,判断X是否已不在窗口之内,不在了,直接移出队列
* 队列的第一个元素是滑动窗口中的最大值
//deque s中存储的是num的下标
//O(n),O(n)
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> res;
deque<int> s;
for(unsigned int i=0;i<num.size();++i){
while(s.size() && num[s.back()]<=num[i])
//从后面依次弹出队列中比当前num值小的元素,同时也能保证队列首元素为当前窗口最大值下标
s.pop_back();
while(s.size() && i-s.front()+1>size)
//当当前窗口移出队首元素所在的位置,即队首元素坐标对应的num不在窗口中,需要弹出
s.pop_front();
s.push_back(i);
//把每次滑动的num下标加入队列
if(size&&i+1>=size)
//当滑动窗口首地址i大于等于size时才开始写入窗口最大值
res.push_back(num[s.front()]);
}
return res;
}
};
class Solution {
public:
//时间复杂度o(n),空间复杂度为o(n)
//思路就是采用双端队列,队列中的头节点保存的数据比后面的要大。
//比如当前假如的数据比队尾的数字大,说明当前这个数字最起码在从
//现在起到后面的过程中可能是最大值
//,而之前队尾的数字不可能最大了,所以要删除队尾元素。
//此外,还要判断队头的元素是否超过size长度,由于存储的是下标,
//所以可以计算得到;
//特别说明,我们在双端队列中保存的数字是传入的向量的下标;
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> vec;
if(num.size()<=0 || num.size()<size ||size<=0) return vec;//处理特殊情况
deque<int> dq;
//处理前size个数据,因为这个时候不需要输出最大值;
for(unsigned int i=0;i<size;i++)
{
//假如当前的元素比队列队尾的元素大,说明之前加入的这些元素不可能是最大值了。因为当前的这个数字比之前加入队列的更晚
while(!dq.empty()&&num[i]>=num[dq.back()])
dq.pop_back();//弹出比当前小的元素下标
dq.push_back(i);//队尾压入当前下标
}
//处理size往后的元素,这时候需要输出滑动窗口的最大值
for(unsigned int i=size;i<num.size();i++)
{
vec.push_back(num[dq.front()]);
while(!dq.empty()&&num[i]>=num[dq.back()])
dq.pop_back();
if(!dq.empty() && dq.front()<=(int)(i-size))
//判断队头的下标是否超出size大小,如果超过,要删除队头元素
dq.pop_front();//删除队头元素
dq.push_back(i);
//将当前下标压入队尾,因为可能在未来是最大值
}
vec.push_back(num[dq.front()]);//最后还要压入一次
return vec;
}
};
题目二:队列的最大值
请定义一个队列并实现函数max得到队列里的最大值,要求函数max、push_back和pop_front的时间复杂度都是O(1)。(剑指offer上的解答)
同上一题相同,我们要寻找队列的最大值,相当与将滑动窗口设置为整个队列。
这里需要使用两个队列,一个队列用来保存入队的数据,一个队列用来保存队列的当前最大值。
同时需要注意出队操作,数据队列出队的同时需要判断其索引是否和当前最大值队列首部索引相同,如果相同则同时也将最大值队列头部出队。
template<typename T> class QueueWithMax {
public:
QueueWithMax() :currentIndex(0) {}
void push_back(T number) {
while (!maximums.empty() && number >= maximums.back().number)
maximums.pop_back();
InternaData internaData = {number, currentIndex};
data.push_back(internaData);
maximums.push_back(internaData);
++currentIndex;
}
void pop_front() {
if (maximums.empty())
throw new exception("queue is empty.");
if (maximums.front().index == data.front().index)
maximums.pop_front();
data.pop_front();
}
T max() const {
if (maximums.empty())
throw new exception("queue is empty.");
return maximums.front().number;
}
private:
struct InternaData {
T number;
int index;
};
deque<InternaData> data;
deque<InternaData> maximums;
int currentIndex;
};