剑指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]}。
【解题思路】
提示:
使用堆(heap)来做
必须存储每一时刻的窗口中的元素吗?
解法:
时间复杂度:O(n),线性复杂度;
空间复杂度:O(w),设窗口的大小为w。
1、最简单的思路,就是每一次窗口滑动之后都遍历一遍窗口中的元素来找出最大值,这样的话,时间复杂度就是O(nw)。
2、好一些的做法,使用堆(heap)来做,堆的大小自然就是窗口的大小w,这会帮助我们很快地找到堆中的最大元素。但是,每一次窗口滑动的时候,我们都需要把不在窗口中的元素从堆中移出去然后再添加一个新进入窗口的元素,这些操作的时间复杂度都是O(log w),因此总的计算下来,完成所有操作的时间复杂度就是O(n log w)。
3、更好的做法,为降低时间复杂度,我们可以使用双向链表或者是vector向量,这样就能在两边插入或者删除元素了。在举例中的第二步,也就是如下所示的这一步:
新元素3比窗口中的2和-5都要大,那么我们完全可以将2和-5从代表窗口的数据结构中移出来,因为不管后面新加进来的元素多大,只需要跟窗口中的最大值比较即可。这个特性对我们降低时间复杂度来说非常重要,基于此,我们有如下算法:
窗口大小为w,数组大小为n,初始状态下窗口为空;
遍历数组中的前w个元素,对每一个元素,执行下列操作:
如果窗口最右端(也就是tail部分)的元素小于或等于当前新加入的元素,那么就将窗口最右端的元素移出窗口,直到窗口最右端元素大于当前新加入元素或者窗口为空为止
将当前元素加入窗口。
用一个例子来解释上面的操作,下面这个数组大小为6,窗口大小为3:
第一步,遍历数组的前3个元素,首先是第一个-4,此时窗口为空,直接加入窗口。
第二步,遍历至第二个元素2,与窗口中最右边元素-4比较,显然2大于-4,因此将-4从窗口中移出,将2加入窗口中。
第三步,遍历至第三个元素-5,与窗口最右边元素2比较,显然2大于-5,直接将-5加入到窗口中。
上述操作保证了窗口中的最大值始终在窗口的最左端,也即是窗口的头部。下面我们继续描述算法:
遍历完前w个元素后,继续遍历数组中的剩余元素,执行下列操作:
将窗口中所有小于或等于当前新加入元素的元素移出,将当前元素加入窗口;
如果窗口最左端的元素的下标超出窗口的范围,则将其移出窗口;
每个时刻窗口中的元素最大值都在窗口的最左端,也就是头部,可以直接输出。
继续用上面的例子来解释剩余的操作:
第四步,遍历至第四个元素1,与窗口中最右端元素-5比较,显然1大于-5,将-5移出窗口,1加入窗口。
第五步,遍历至第五个元素-1,与窗口最右端元素1比较,显然1大于-1,直接将-1加入窗口;同时窗口最左端元素2超出窗口范围,从窗口中移出。
第六步,遍历至第六个元素6,显然6比窗口中的所有元素都打,因此将所有元素移出,将6加入到窗口中。
每一步窗口中元素的最大值,都在窗口的最左端,也就是窗口头部,直接输出即可。在上述算法中,每一个元素不管是被插入还是移出,都是只有一次,因此就是遍历一遍数组复杂度O(n)。
【代码实现】
import java.util.ArrayList;
import java.util.LinkedList;
/**
* @Author: makexin
* @Date: 2019/12/911:15
*/
public class Solution {
public ArrayList<Integer> maxInWindows(int [] num, int size)
{
ArrayList<Integer> res = new ArrayList<>();
if (size > num.length || size==0)
return res;
LinkedList<Integer> queue = new LinkedList<>();
LinkedList<Integer> index = new LinkedList<>();
for (int i = 0; i<size-1; i++)
{
while (!queue.isEmpty() && queue.getLast()<=num[i])
{
queue.removeLast();
index.removeLast();
}
queue.addLast(num[i]);
index.addLast(i);
}
for (int i = size-1; i<num.length; i++)
{
if (!index.isEmpty() && index.getFirst() <= i-size)
{
queue.removeFirst();
index.removeFirst();
}
while (!queue.isEmpty() && queue.getLast()<=num[i])
{
queue.removeLast();
index.removeLast();
}
queue.addLast(num[i]);
index.addLast(i);
res.add(queue.getFirst());
}
return res;
}
}