头条一面挂的另一个题目,是个原题,面试官小哥哥本想出个简单题给我试试水。面试前在剑指offer上看到了原题,以为自己知道的了思想,其实不然。
首先从这个滑动窗口最大值入手
题目描述
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。
实现方法: 我们可以从最大值栈中得到类似的启示,已经入队列的,那么直到这个值被弹出,都可能作为一个最大值出现,一个值如果在他的后面入了一个比其大的数,他一定不是最大值,一个最大值只有在出队的时候才会消失,这个出队的时机我们是知道的。
因此,定义一个双向队列deque,一直从back入队列,从front得到最大值,以及正常的出队列。
而back这边,用于处理不可能成为最大值的值。
有这么几种情况:
当队列中有一个较小值如2,此时入队列了一个较大值,如5,那么这个队列中的2再也不不可能作为最大值出现,直接从back处弹掉,5入队列成为新的最大值。
另一种情况,当5在队列中时,3入队,发现3比5小,那么当5被正常弹出时,3可能会成为一个队列最大值,那么3是一定要保留的,因此3入队
当5、3在队列中,4入队时,发现4比3大,那么3已经毫无用处了,因为即使5出队了,3也不可能是一个最大值,而是4,那3从back的位置出队,4对比5发现其比5小,那么4可能作为一个最大值出现,那么4入队。
还有一种情况即正常的出队操作,当数据队列pop队首元素时,对比max队列中的队首是否是被弹出的值,是,一起弹出,不是,不予理睬。
注意我们存储的是元素在数组中的下标,而不是值本身,存储下标是为了检查滑动队首元素是否在滑动窗口范围内,当当前入队元素所在的数组下标值与队首元素的下标值作差的结果大于滑动窗口大小时,说明队首元素早就过期,应该弹出,不属于目前的滑动窗口内。
同时我们也可以根据下标得到数组中的具体值,非常方便
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int>ans;
if(num.size()<size)return ans;
deque<int>q;
for(int i=0;i<num.size();++i)
{
if(i+1<size)
{
q.push_back(i);
if(num[i]>num[q.front()])q.pop_front();
continue;
}
while(!q.empty()&&num[i]>num[q.back()])
{
q.pop_back();
}
q.push_back(i);
if(!q.empty()&&i-q.front()+1>size)q.pop_front();
if(!q.empty())ans.push_back(num[q.front()]);
}
return ans;
}
};
因为这里是滑动窗口,固定size的队列,因此主要size来限定队列中的元素,此处的“正常弹出操作”即滑动窗口爆满,需要从front处出队列
整体操作就是一个单调队列的行为,当然也有单调栈的思想
接着是队列最大值,队列最大值是主动插入弹出的,是有函数控制的,而不是像滑动窗口一样通过窗口大小限定来自动操作出队入队。
若是队列形式,那么我们要记录两个信息,一个是入队时间,也就是在滑动窗口中的下标,另一个是值本身。因此该队列存储一个结构体。
我们的入队时间用一个变量记录,当有新元素到来时,直接递增值大小即可。其他入队方式和对比大小的方式与滑动窗口中的一模一样。
注意因为出队列是主动控制,我们要对比存储最大值的队列队首的时间是否与当前pop出去的元素时间一致,若一致说明该元素在最大值队列中已经失效,也必须出队列。保证最大值队列的时效性。
template<typename T> class QueueWithMax
{
public:
QueueWithMax(): currentIndex(0){}
void push_back(T number)//入队
{
while(!maximums.empty()&&number>=maximums.back().number)//消去最大值队列中不可能是最大值的元素
maximums.pop_back();
InternalData internalData={number,currentIndex};
data.push_back(internalData)
maximums.push_back(internalData);
++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 InternalData//结构体保存入队元素信息,值与入队时间
{
T number;
int index;
};
deque<InternalData>data;//数据队列
deque<InternalData>maximums;//最大值队列
int currentIndex;//递增时间下标
}