滑动窗口

905 阅读2分钟

解决的问题

窗口只能右边界或左边界向右滑的情况下,且左边界要小于右边界,维持窗口内部最大值或者最小值快速更新的结构。

窗口更新结构的原理

功能的实现用双端队列。

LinkedList<Integer> qmax = new LinkedList<>();//双端队列

image.png /////////////////////////////////////////////////////////////////////////////////////////////////

image.png /////////////////////////////////////////////////////////////////////////////////////////////////

image.png /////////////////////////////////////////////////////////////////////////////////////////////////

时间复杂度

时间复杂度:每个位置最多进一次,最多出一次,所以双端队列更新的总代价一定是O(N),单次平均更新的代价是O(N)/N也就是O(1)

代码结构

    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 LinkedList<>();
        }

        //将R一直扩到数组结尾,这个只是演示代码,具体的题目需要具体的定制
        public void addNumFromRight(){
            if (R == arr.length){
                return;
            }
            //双端队列非空且位于尾部的值如果小于要加入的值则弹出
            while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]){
                qmax.pollLast();
            }
            //为空直接就加入
            qmax.addLast(R);
            R++;
        }

        //窗口的L依次滑动到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;
        }
    }

相关题目

有一个整型数组arr和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置(L、R同时移动)。

例如,数组为[4,3,5,4,3,3,6,7],窗口大小为3时:

[4 3 5]4 3 3 6 7 窗口中最大值为5

4[3 5 4]3 3 6 7 窗口中最大值为5

4 3[5 4 3]3 6 7 窗口中最大值为5

4 3 5[4 3 3]6 7 窗口中最大值为4

4 3 5 4[3 3 6]7 窗口中最大值为6

4 3 5 4 3[3 6 7] 窗口中最大值为7

请实现一个函数。 输入:整型数组arr,窗口大小为w。

输出:一个长度为n-w+1的数组res,res[i]表示每一种窗口状态下的 以本题为例,结果应该返回{5,5,5,4,6,7}。

思路分析

所用的方法是上面的结构,这个窗口是让R向右一步的时候,同时让L向右移动一步过期

相关代码

1-双端队列

用i - w以及及时弹出实现窗口的固定,体会这个方法,很多题都会用到

时间复杂度O(n)

    //该代码没有刻意使用L、R。用i - w以及及时弹出实现窗口的固定,因为i-w+1就是窗口的左边界
    public static int[] getMaxWindow(int[] arr, int w){
        if (arr == null || w < 1 || arr.length < w){
            return null;
        }
        LinkedList<Integer> qmax = new LinkedList<>();
        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);
           
            //i - w得到滑动窗口的左边界L,刚开始的时候滑动窗口还没铺满w个的时候,肯定是直接跳过
            //队列中的头地址和i-w相等,弹出,注意i - w + 1才是窗口的左边界,这里是将滑动后残留             //的上一个窗口周期的最大值剔除掉,也就是这个值属于上一周期的,不是当前窗口周期的
            //很妙的代码,可以结合下一个if()看
            if (qmax.peekFirst() == i - w){
                qmax.peekFirst();
            }
            //临界点表示的是滑动窗口已经铺满w个[4 3 5],i这个时候i是2,w是3,所以i + 1 = w
            //i大于这个值就可以存入最大值数组了,i代表arr[2]的时候就可以,因为w = 3,所以满足滑             //动窗口的大小了
            if (i >= w - 1){
                //注意双端队列中存的是地址,所以arr[]
                res[index++] = arr[qmax.peekFirst()];
            }
        }
        return res;
    }

    public static void printArray(int[] arr) {
        for (int i = 0; i != arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int[] arr = { 4, 3, 5, 4, 3, 3, 6, 7 };
        int w = 3;
        printArray(getMaxWindow(arr, w));
    }
2-优先级队列(堆)

时间复杂度O(nlogn)

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] pair1, int[] pair2) {
                //维持大根堆,如果两个值相等,就按照位置的大小进行比较
                return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
            }
        });
        for (int i = 0; i < k; ++i) {
            pq.offer(new int[]{nums[i], i});
        }
        int[] ans = new int[n - k + 1];
        ans[0] = pq.peek()[0];
        for (int i = k; i < n; ++i) {
            pq.offer(new int[]{nums[i], i});
            //看看是不是位置不在规定的窗口内,不在就弹出
            while (pq.peek()[1] <= i - k) {
                pq.poll();
            }
            ans[i - k + 1] = pq.peek()[0];
        }
        return ans;
    }
}

更多信息

leetcode 239题

leetcode 220题