单调栈和窗口及其更新结构

滑动窗口

  1. 滑动窗口是一种想象出来的数据结构
  2. 有左边界L和有边界R
  3. 在数组或字符串或者一个序列上,记为S,窗口就是S[L...R]这一部分
  4. L往右滑以为着一个样本出了窗口,R往右滑意味着一个样本进了窗口
  5. L和R都是只能往右滑

作用

     滑动窗口首位指针等技巧,是一种求解问题的流程设计

滑动窗口内最大值和最小值的更新结构

  1. 窗口不管L还是R滑动之后,都会让窗口呈现新状况
  2. 利用单调双端队列(LinkedList)
  3. 窗口内是从左到右变小的,头部位置的数代表整个窗口的最大值
  4. 双端队列不让R动只让L往右动表示哪些数会依次成为最大值的优先级

栗子1:

         假设一个固定大小为W的窗口,依次划过arr。返回每一次滑出状况的最大值。arr={4,3,5,4,3,3,6,7},w=3,返回[5,5,5,4,6,7]

      public static int[] maxSlidingWindow(int[] nums, int k) {
          if(nums.length < k || nums == null) {
              return null;
          }
          
          //nums.length - k + 1表示要返回的长度
          int[] res = new int[nums.length - k + 1];
          
          int index = 0;
          //双端队列,存放窗口的下标
          LinkedList<Integer> qmax = new LinkedList<>();
          
        for(int R = 0; R < nums.length ; R ++) {
            //R从右边弹出与添加的条件。弹出:双端队列中的值小于即将要添加的值
            while(!qmax.isEmpty() && nums[qmax.peekLast()] <= nums[R]) {
                qmax.pollLast();
            }
            qmax.addLast(R);
            //双端队列左边的值弹出的条件:R - k表示要弹出的下标,例如R=4,k=3,则要弹出下标为1,只剩下2,3,4三个数
            if(qmax.peekFirst() == R - k) {
                qmax.pollFirst();
            }
            //以上窗口更新做完了
           
            //窗口形成w之后收集答案,没形成w之前不收集答案
            if(R >= k-1) {
                res[index++] = nums[qmax.peekFirst()];
            } 
        }
        return res;   
      }

栗子2:

   给定一个整型数组arr,和一个整数num。某个arr中的子数组sub,如果想达标,必须满足:sub中最大值-sub中最小值 <=num,返回arr中达标子数组的数量

public static int num(int[] arr, int sum) {
        if (arr == null || arr.length == 0 || sum < 0) {
            return 0;
        }
        int N = arr.length;
        int count = 0;
        LinkedList<Integer> maxWindow = new LinkedList<>();//最大值窗口
        LinkedList<Integer> minWindow = new LinkedList<>();//最小值窗口
        int R = 0;
        //L是开头位置,尝试每一个开头,0位置达标的多少个,1位置达标的多少个,R滑到违规停
        for (int L = 0; L < N; L++) {
            //如果窗口此时的开头为L,下面的while工作:R向右扩到违规为止
            while (R < N) {//R是最后一个达标位置的再下一个
                while (!maxWindow.isEmpty() && arr[maxWindow.peekLast()] <= arr[R]) {
                    maxWindow.pollLast();
                }
                maxWindow.addLast(R);
                while (!minWindow.isEmpty() && arr[minWindow.peekLast()] >= arr[R]) {
                    minWindow.pollLast();
                }
                minWindow.addLast(R);
                //一旦不达标break
                if (arr[maxWindow.peekFirst()] - arr[minWindow.peekFirst()] > sum) {
                    break;
                } else {
                    R++;
                }
            }
            
            //R是最后一个达标位置的下一个位置,第一个违规的位置,以L开头,到R位置一共有多少达标的。例如R=5,L=0,0~0,0~1,0~2,0~3,0~4共5个达标
            count += R - L;
            //L更新
            if (maxWindow.peekFirst() == L) {
                maxWindow.pollFirst();
            }
            if (minWindow.peekFirst() == L) {
                minWindow.pollFirst();
            }
        }
        return count;
    }

 单调栈

 一种特别设计的栈结构,解决如下问题:

  1. arr[i]的左侧离i最近并且小于(或者大于)arr[i]的位置在哪
  2. arr[i]的右侧离i最近并且小于(或者大于)arr[i]的位置在哪

栗子1:

数组{3,2,1,4,5},返回[  [-1,1],[-1,2],[-1,-1],[2,-1],[3,-1] ]

//无重复值
    public static int[][] getNearLessNoRepeat(int[] arr) {
        int[][] res = new int[arr.length][2];
        // 单调栈,只存位置!
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < arr.length; i++) { // 当遍历到i位置的数,arr[i]
            while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {
                int j = stack.pop();
                int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();
                res[j][0] = leftLessIndex;
                res[j][1] = i;
            }
            stack.push(i);
        }
        while (!stack.isEmpty()) {
            int j = stack.pop();
            int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();
            res[j][0] = leftLessIndex;
            res[j][1] = -1;
        }
        return res;
    }
    
    
    //有重复值
    public static int[][] getNearLess(int[] arr) {
        int[][] res = new int[arr.length][2];
        //List<Integer>:放的是位置,同样值的东西,位置压在一起
        //代表值: 底->顶  小->大
        Stack<List<Integer>> stack = new Stack<>();
        for (int i = 0; i < arr.length; i++) { // i -> arr[i] 进栈
            //下面的值大于当前值
            while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {
                List<Integer> popIs = stack.pop();
                //取位于下面位置的列表中,最晚加入的那个.get(stack.peek().size() - 1):最晚加入的
                int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);
                for (Integer popi : popIs) {
                    res[popi][0] = leftLessIndex;
                    res[popi][1] = i;
                }
            }
            
            //等于的,比你小的
            if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) {
                stack.peek().add(Integer.valueOf(i));
            } else {
                ArrayList<Integer> list = new ArrayList<>();
                list.add(i);
                stack.push(list);
            }
        }
        while (!stack.isEmpty()) {
            List<Integer> popIs = stack.pop();
            //取位于下面位置的列表中,最晚加入的那个
            int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);
            for (Integer popi : popIs) {
                res[popi][0] = leftLessIndex;
                res[popi][1] = -1;
            }
        }
        return res;
    }

栗子2:

 给定一个只包含正数的数组arr,arr中任何一个子数组sub,一定都可以算出(sub累加和)*(sub中的最小值)是什么,所有子数组中,这个值最大是多少?

 

滑动窗口,单调栈怎么用

想用滑动窗口,要想办法把具体的问题转化为滑动窗口的处理流程

想用滑动窗口最值的更新结构,就看处理流程下,是否需要最值这个信息

想用单调栈,要想办法把具体的问题转化为单调栈所解决的原问题

 

posted @ 2021-09-28 15:46  YanickFan  阅读(85)  评论(0)    收藏  举报