目录
滑动窗口的引入
由一个代表题目,引出一种结构。
【题目】有一个整型数组arr和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。例如,数组为[4,3,5,4,3,3,6,7],窗口大小为3时:
[4 3 5]4 3 3 6 7
4[3 5 4]3 3 6 7
4 3[5 4 3]3 6 7
4 3 5[4 3 3]6 7
4 3 5 4[3 3 6]7
4 3 5 4 3[3 6 7]
窗口中最大值为5,窗口中最大值为5,窗口中最大值为5,窗口中最大值为4,窗口中最大值为6,窗口中最大值为7,如果数组长度为n,窗口大小为w,则一共产生n-w+1个窗口的最大值。 请实现一个函数。输入:整型数组arr,窗口大小为w。输出:一个长度为n-w+1的数组res,res[i]表示每一种窗口状态下的最大值,以本题为例,结果应该返回{5,5,5,4,6,7}。
对于这个问题的求解采用的正是下面介绍的算法的思想,操作更简单,因为它的窗口的大小是固定的,具体的操作的思想可以先看下面的介绍,这道题目的代码的具体实现如下:
public static int[] getMaxWindow(int[] arr, int w) {//w为窗口大小
if (arr == null || w < 1 || arr.length < w) {
return null;
}
LinkedList<Integer> qmax = new LinkedList<Integer>();//定义一个双端队列
int[] res = new int[arr.length - w + 1];
int index = 0;
for (int i = 0; i < arr.length; i++) {
while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]) {//如果当前队列不为空并且右侧窗口滑动要加入的位置大于等于队列中最后一个位置的值,队列中最后一个位置的值直接出队列
qmax.pollLast();
}
qmax.addLast(i);//当前位置的值加进去
if (qmax.peekFirst() == i - w) {//i-w表示左边界,如果队列中第一个位置的值对应数组中的下标和左边界来到位置相同,队列的第一个位置的值出队列,否则不出队列。因为可能存在右窗口滑动的过程中,左边界来到位置的值已经出了队列的情况。
qmax.pollFirst();
}
if (i >= w - 1) {//如果窗口已经形成
res[index++] = arr[qmax.peekFirst()];//队列的第一个位置的值就是当前的最大值
}
}
return res;
}
窗口内最大值与最小值更新结构的原理与实现
对于滑动窗口的移动可以看作两个边界的移动,假设左边界为L,右边界为R,通过两个边界的移动来比拟窗口的滑动,而对于窗口内最大最小值的更新采用的是双端队列结构,以窗口内最大值更新为例,定义一个双端队列,滑动窗口一开始右边界R位于最左侧,随后R边界开始移动,先向右移动一个位置,第一个值进来队列,此时窗口内最大值只有它,随后右边界继续移动,如果随后加入的值比当前队列的值小,那么直接从队列后面加入,保证的是队列的头部一定是最大值,如果比队列当前的值大,那么队列当前的值出队列,然后目前的值进入,后续的操作相同,如果遇到前面的值比自己小或者相等的情况,直接出队列,一直保证队列头部是最大值。而如果左边界也向右移动,那么就是队列的头部开始出队列,正因为这样,也解释了为什么后进来的值和前面的值相同时,前面的值也要出队列,因为两者的值相同,前面的值出去的快,肯定不会选择留下它。这正是窗口内最大值更新结构的原理和实现,最小值的更新结构的实现同理。
滑动窗口的详解
滑动窗口的原理和实现正是上面所述,具体实现的代码如下:
public static class WindowMax{
private int L;//滑动窗口的左边界
private int R;//滑动窗口的右边界
private int[] arr;
private LinkedList<Integer> qmax;
public WindowMax(int[] a){//初始化
arr = a;
L = -1;
R = 0;
qmax = new LinkecList<>();
}
public void addNumFromRight(){//窗口右边界向右移动
if(R == arr.length){
return;
}//如果目前右边界来到了最右侧,直接返回。
while(!qmax.isEmpty()&&arr[qmax.peekLast()] <= arr[R]){//如果当前队列不为空并且右侧窗口滑动要加入的位置大于等于队列中最后一个位置的值,队列中最后一个位置的值直接出队列
qmax.pollLast();
}
qmax.addLast(R);//队列中目前的值都比要加入队列的值大,加入队列
R++;//右边界继续向下移动
}
public void removeNumFromLeft(){
if(L >= R-1){
return;
}//如果左边界来到最右侧,直接返回
L++;//左边界向右移动一个位置
if(qmax.peekFirst() == L){//如果队列中第一个位置的值对应数组中的下标和左边界来到位置相同,队列的第一个位置的值出队列,否则不出队列。因为可能存在右窗口滑动的过程中,左边界来到位置的值已经出了队列的情况。
qmax.pollFirst();
}
}
public Integer getMax(){//返回最大值
if(!qmax.isEmpty()){
return arr[qmax.peekFirst()];//如果队列不为空,队列的第一个元素就是最大值
}
return null;
}
}