滑动窗口及其窗口内最大值最小值更新结构

174 阅读1分钟

一、窗口的概念

  1. l,r指针,一个指向窗口的右边,一个指向窗口的左面。

  2. l,r指针只能往右走,不能回退。

  3. l不能超过r.

二、窗口内最大值最小值更新结构

就是在窗口进行加数和减少数的时候,通过一个双向列表,配合得出窗口在任何时刻,最大值或者最小值的值是多少。o(1)的时间复杂度

最大值更新结构

链表:左面是头,右面是尾。加入数据是从尾加入进来的。从左往右必须严格的由大到小。(不能有等于,等于跟小于一样的处理。)

最小值更新结构

区别:从左往右由小到大。

三、例题

1.生成窗口最大值数组

image.png

滑动窗口的简单应用,最大值更新结构。

代码实现:

public class chuangkou {
    public static void main(String[] args) {
        int[] a = new int[]{4,3,5,4,3,3,6,7};
        System.out.print(Arrays.toString(window(a,3)));
    }

    private static int[] window(int[] a, int num) {
        int l = -1;
        int r = 0;
        //窗口最大值更新结构
        LinkedList<Integer> list = new LinkedList<>();
        //保存结果
        int index = 0;
        int[] array = new int[a.length-num+1];
        //左边
        while(l<a.length){
            //右边
            while(r<a.length){
                System.out.println("======="+a[r]);
                //1。窗口有值了,要进行list的加入更新。
                //当list里有值,并且尾部的值小于a[r]当前进入的数出链表,因为是最大值更新结构,要严格的
                //从大到小。
                while(!list.isEmpty()&&a[list.peekLast()]<=a[r]){
                    list.pollLast();
                }
                //2。当没内容了,或者当前值刚好小于链表尾部的值则从尾部加入数据
                list.addLast(r);
                //3.当窗口里的数等于3则跳出,进行减小窗口的逻辑。
                if(r-l==3){
                    array[index++] = a[list.peekFirst()];
                    System.out.println(array[index-1]);
                    break;
                }
                //4.r右移继续加数
                r++;
            }

            if(r-l<3){
                break;
            }
            //5.当前窗口内容是3,进行数据减少,并更新list
            l++;
            r++;
            if(!list.isEmpty()&&list.peekFirst()<=l){
                list.pollFirst();
            }
        }

        return array;
    }
}

2.最大值减去最小值小于等于num的子数组数量。

image.png

子数组是连续的,当涉及到连续的最大值和最小值的问题,就可以使用滑动窗口的最大值最小值更新结构。

解题思路:

1.当子数组已经满足(最大值-最小值<=num)即便再减数,依然会满足。(减数只会让最大值越小,最小值越大)
2.当子数组不满足(最大值-最小值<=num)即便增加数,依然不满足条件。(减数只会让最大值更大,最小值更小)
3.根据上面的两个条件,我们可以遍历一遍,找到以i开头的所有满足条件的子数组。

注意事项:

  1. 计算结果不要安排在加数的判断里,这样会导致最后都满足条件的以i开头的所有结果全部漏掉。

  2. l,r的初始值,以及++的逻辑位置要确定好。

代码实现:

public static int window2(int[] a,int num){
    int l = 0;
    int r = 0;//进入第一个数。
    LinkedList<Integer> biglist = new LinkedList<>();
    LinkedList<Integer> smalllist = new LinkedList<>();
    int res = 0;
    while(l<a.length){
        while(r<a.length){
            //1.最大值更新结构的加数逻辑,当list里的值从尾部开始小于等于加入的值则取出。
            while(!biglist.isEmpty()&&a[biglist.peekLast()]<=a[r]){
                biglist.pollLast();
            }
            biglist.addLast(r);
            //2.最小值更新结构的加数逻辑
            while(!smalllist.isEmpty()&&a[smalllist.peekLast()]>=a[r]){
                smalllist.pollLast();
            }
            smalllist.addLast(r);
            //3.当刚不满足条件的进行记录,并跳出进行减数操作。
            //条件是:最大值-最小值<=num,当不满足的时候,跳出循环
            if(a[biglist.peekFirst()]-a[smalllist.peekFirst()]>num){
                //计算次数的运算不能写在这里,当最后一波就算都满足要求的时候会没办法进入判断,进入累加。
                break;
            }
            r++;
        }
        //4.跳出则进入减数逻辑。
        //最大值更新结构的减数
        if(biglist.peekFirst()<=l){
            biglist.pollFirst();
        }
        //最小值更新结构
        if(smalllist.peekFirst()<=l){
            smalllist.pollFirst();
        }
        //计算结果安排在这里。
        res += r-l;
        l++;
        //这块不需要r增加。
    }
    return res;
}