题目描述
- 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。
- 地址:牛客链接
问题分析
- 非常经典的滑动窗口问题,只不过该题是固定窗口大小,相对简单一点
- 如何维护滑动窗口的最大值?
- 维护最大值时,用双端队列,队列内存放数组索引(因为原数组中可能会有重重复元素,存index则不会重复),使索引位置上的数从大到小。
- 当right向右移动时,窗口加数。只有当队列非空,并且队尾元素小于等于待加入元素时,弹出队尾元素(等于是为了当出现重复元素时,让索引是最新索引(靠后的索引))。直至队尾元素大于待加入元素。将待加入元素加入队尾。
- 当left向右移动时,窗口减数。检查过期的index是否是队首元素,若是,则将队首元素从队首出队。
- 如何维护滑动窗口的最小值
- 维护最小值时,用双端队列,队列内存放数组索引,使索引位置上的数从小到大。
- 当right向右移动时,窗口加数。只有当队列非空,并且队尾元素大于等于待加入元素时,弹出队尾元素。直至队尾元素小于待加入元素。将待加入元素加入队尾。
- 当left向右移动时,窗口减数。检查过期的index是否是队首元素,若是,则将队首元素从队首出队。
- 回到问题本身上来,本题方法1便是用了left 与right 来指示一个窗口左边界和右边界。但由于窗口大小是固定的,所以也可以只用一个值来确定窗口右边界即可。只要在右边界向右走之后,检查maxWin里面维护的最大值是否已经出窗即可。如果
i - winMax.peekFirst() + 1 > size
说明 num[winMax.peekFirst()] 已经出窗,所以需要将winMax中第一个元素出队。
经验教训
- 滑动窗口内的最大值
- 滑动窗口内的最小值
代码实现
- left 与 right 法
public ArrayList<Integer> maxInWindows1(int [] num, int size) {
ArrayList<Integer> res = new ArrayList<>();
if (num == null || size <= 0 || num.length < size) {
return res;
}
//left,right用来指示窗口的大小
int left = 0;
int right = 0;
LinkedList<Integer> winMax = new LinkedList<>();
//先装入size个
while(right - left <= size - 1) {
//使winMax中从前到后存储的index,它们所指示的元素从大到小的。
while (! winMax.isEmpty() && num[winMax.peekLast()] <= num[right]) {
winMax.pollLast();
}
winMax.addLast(right);
++right;
}
//得到第一个窗口的值
res.add(num[winMax.peekFirst()]);
while (right < num.length) {
//此时,开始计算一个新窗口,right已经后移,需要将left同样后移,这样才是一个新窗口
//左边出窗
//若最大值索引已经被移出窗外,那么需要从winMax中删除
int expireIndex = left;
if (expireIndex == winMax.peekFirst()) {
winMax.pollFirst();
}
//left后移
++left;
//右边入窗
while (! winMax.isEmpty() && num[winMax.peekLast()] <= num[right]) {
winMax.pollLast();
}
winMax.addLast(right);
//将新窗口的最大值加入结果集中
res.add(num[winMax.peekFirst()]);
//right后移,计算下一窗口
++right;
}
return res;
}
- 简洁法
public ArrayList<Integer> maxInWindows(int [] num, int size) {
if (num == null || size <= 0 || num.length < size) {
return new ArrayList<Integer>();
}
ArrayList<Integer> res = new ArrayList<>();
LinkedList<Integer> winMax = new LinkedList<>();
//i便可以看做窗口的右边界
for (int i = 0; i < num.length; i++) {
//当winMax队首元素所指向的元素因为i的增加不在窗口内时,需要从winMax中移除
if (!winMax.isEmpty() && i - winMax.peekFirst() + 1 > size) {
winMax.pollFirst();
}
//右边入窗
while(!winMax.isEmpty() && num[winMax.peekLast()] <= num[i]) {
winMax.pollLast();
}
winMax.addLast(i);
//只有当形成size大小窗口时,才加入到结果集中
if (i >= size - 1) {
res.add(num[winMax.peekFirst()]);
}
}
return res;
}