zoukankan      html  css  js  c++  java
  • 经典结构 窗口最大值更新结构

    内容:

    1、窗口内最大值更新结构

    2、窗口移动

    3、求达标的子数组个数

    1、窗口内最大值更新结构

    窗口:数组中的一系列数

    • L与R之间的数就是窗口内的数 
    • L和R的初始位置为数组的左边,可表示为-1
    • L和R都只能右移,不可后退;且L不可超过R

    窗口内最大值更新结构实质上就是一个双端队列(双向链表实现),可以从头部放入数据、弹出数据,也可以从尾部放入数据、弹出数据(均是O(1))

    双端队列中每个元素由value和index组成,value是值,index是时间戳(数组下标)

    双向队列加数策略:R右移,则向双向队列中加数;如果尾部元素比要加入的数小,则弹出尾部的数,一直弹,

    直到尾部的数大于新加入的数,或者队列为空。

    解释:如果新加入的数据,比之前的数大,那么之前的数就不可能是窗口内最大值,可以舍弃掉。

    双向队列减数策略: L右移,则需要处理,如果刚被移出窗口的数是队列头部的数则把队列头部的数弹出,这就是为什么要保存数组下标的原因

    窗口内最小值类似:双向队列从头到尾依次递增,加数如果尾部数比要加的数大则弹出,减数也是过期就弹出;

    代码:

     1 // 最大值窗口更新结构
     2 public class MaxValueWindow {
     3     
     4     private LinkedList<Integer> queue;
     5     public MaxValueWindow(){
     6         this.queue = new LinkedList<Integer>();
     7     }
     8     
     9     // 更新窗口最大值
    10     public void add(int i){
    11         while(!queue.isEmpty() && queue.getLast() <= i){
    12             queue.pollLast();    
    13         }
    14         queue.addLast(i);        
    15     }
    16     
    17     // 获取窗口最大值
    18     public int getMax(){
    19         if(!queue.isEmpty()){
    20             return queue.peekFirst();    
    21         }
    22         return Integer.MIN_VALUE;
    23     }
    24     
    25     // 使窗口最大值过期
    26     public void expireMaxValue(){
    27         if(!queue.isEmpty()){
    28             queue.pollFirst();
    29         }
    30     }
    31     
    32     public static void main(String[] args) {
    33         MaxValueWindow window = new MaxValueWindow();
    34         window.add(6);
    35         window.add(4);
    36         window.add(9);
    37         window.add(8);
    38         System.out.println(window.getMax());    // 9
    39         window.expireMaxValue();
    40         System.out.println(window.getMax());    // 8
    41     }
    42 }

    2、窗口移动

    题目描述:

    有一个整形数组arr和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右滑动一个位置

    例如:数组为[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

    如果数组长度为n 窗口大小为w 则一共产生n-w+1个窗口的最大值

    请实现一个函数:

    输入:整形数组arr  窗口大小w

    输出:一个长度为n-w+1的数组res  res[i]表示每一种窗口状态下的最大值

    以上面的数组为例,结果应该返回[5, 5, 5, 4, 6, 7]

    思路:

    前面介绍的窗口大值更新结构的特性是,先前放入的数如果还存在于结构中,那么该数一定比后放入的数都大。

    此题窗口移动的过程就是从窗口中减一个数和增一个数的过程

    代码:

     1 public class SlidingWindowMaxArray {
     2     public static int[] getMaxWindow(int[] arr, int w) {
     3         if (arr == null || w < 1 || arr.length < w) {
     4             return null;
     5         }
     6         LinkedList<Integer> qmax = new LinkedList<Integer>();
     7         int[] res = new int[arr.length - w + 1];
     8         int index = 0;
     9         for (int i = 0; i < arr.length; i++) {
    10             while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]) {
    11                 qmax.pollLast();
    12             }
    13             qmax.addLast(i);
    14             if (qmax.peekFirst() == i - w) {
    15                 qmax.pollFirst();
    16             }
    17             if (i >= w - 1) {
    18                 res[index++] = arr[qmax.peekFirst()];
    19             }
    20         }
    21         return res;
    22     }
    23     
    24     public static void main(String[] args) {
    25         int[] arr = {4, 3, 5, 4, 3, 3, 6, 7};
    26         System.out.println(Arrays.toString(getMaxWindow(arr, 3)));
    27     }
    28 }

    3、求达标的子数组个数

    题目描述:求最大值减去最小值小于或等于num的子数组数量

    给定数组arr和整数num,共返回有多少个子数组满足如下情况:

    max(arr[i...j]) - min(arr[i..j])  <=  num

    max(arr[i...j])表示子数组arr[i...j]中的最大值,min(arr[i...j])表示子数组arr[i...j]中的最小值

    要求:实现时间复杂度O(N)的算法

    暴力思路:

    遍历每个元素,再遍历以当前元素为首的所有子数组,再遍历子数组找到其中的大值和小值以判断其是否达标。

    很显然这种方法的时间复杂度为 o(N^3) ,但如果使用窗口最大值更新结构,则能实现 O(N) 级别的解

    代码: 

     1     // 暴力方法(O(N^3))
     2     public static int getNum1(int[] arr, int num) {
     3         int res = 0;
     4         // 遍历所有子数组
     5         for (int start = 0; start < arr.length; start++) {
     6             for(int end=start;end<arr.length;end++){
     7                 if(isValid(arr, start, end, num)){
     8                     res++;
     9                 }
    10             }
    11         }
    12         return res;
    13     }
    14     
    15     public static boolean isValid(int[] arr, int start, int end, int num){
    16         // 判断子数组最大值和最小值是否小于num
    17         int max = Integer.MIN_VALUE;
    18         int min = Integer.MAX_VALUE;
    19         for(int i=start; i<=end;i++){
    20             max = Math.max(max, arr[i]);
    21             min = Math.min(min, arr[i]);
    22         }
    23         return max - min <= num;
    24     }

    窗口最大值更新结构思路:

    基本思想:

    • 前提条件:用 L 和 R 两个指针指向数组的两个下标,且 L 在 R 的左边,下面两条是必然发生的
    • 当 L~R 这一子数组达标时,可以推导出以 L 开头的长度不超过 R-L+1 的所有子数组都达标
    • 当 L~R 这一子数组不达标时,无论 L 向左扩多少个位置或者 R 向 右扩多少个位置, L~R 还是不达标

    O(N) 的解对应的算法是:

    L 和 R 都从0开始, R 先向右移动, R 每右移一个位置就使用窗口最大值更新结构和窗口最小值更新结构

    记录一下 L~R 之间的大值和小值的下标,当 R 移动到如果再右移一个位置 L~R 就不达标了时停 止,这时以当前 L开头

    的长度不超过 R-L+1 的子数组都达标;然后 L 右移一个位置,同时更新一下结构( L-1 下标过期),

    再右移 R 至 R 如果右移一个位置 L~R 就不达标了停止(每右移 R 一次也更新结构)……;直到

    L 到达数组尾元素为止。将每次 R 停止时, R-L+1 的数量累加起来就是 O(N) 的解,因为 L 和 R 都只向右移动,

    并且每次 R 停止时,以 L 开头的达标子串的数量直接通过 R-L+1 计算,所以 时间复杂度就是将数组遍历了一遍即 O(N)

    代码:

     1     // O(N)的解法
     2     public static int getNum(int[] arr, int num) {
     3         if (arr == null || arr.length == 0) {
     4             return 0;
     5         }
     6         LinkedList<Integer> qmin = new LinkedList<Integer>();
     7         LinkedList<Integer> qmax = new LinkedList<Integer>();
     8         int L = 0;
     9         int R = 0;
    10         int res = 0;
    11         while (L < arr.length) {
    12             while (R < arr.length) {
    13                 // R往右扩 扩到不能扩停
    14                 while (!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[R]) {
    15                     qmin.pollLast();
    16                 }
    17                 qmin.addLast(R);
    18                 while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]) {
    19                     qmax.pollLast();
    20                 }
    21                 qmax.addLast(R);
    22                 if (arr[qmax.getFirst()] - arr[qmin.getFirst()] > num) {
    23                     // 不达标的情况
    24                     break;
    25                 }
    26                 R++;
    27             }
    28             // 更新最大最小值
    29             if (qmin.peekFirst() == L) {
    30                 qmin.pollFirst();
    31             }
    32             if (qmax.peekFirst() == L) {
    33                 qmax.pollFirst();
    34             }
    35             // 加上已统计的结果
    36             res += R - L + 1;
    37             // L右扩
    38             L++;
    39         }
    40         return res;
    41     }    
  • 相关阅读:
    [VB]VB操作Execl大全
    架构实战软件架构设计的过程
    “如何管理软件企业”之软件行业的三类主要盈利模式
    交互设计指南
    编写高质量代码Web前端开发修炼之道
    《裂变——看得见的未来》新书首发式暨读者联欢会
    只懂执行其他人测试想法的人,不能算真正的测试人
    敏捷联盟Gordon Pask奖获得者讲“测试驱动开发”(TDD)
    《裂变》作者爆料:欺世盗名者绝非唐骏一人
    《团队之美》的那些作者们
  • 原文地址:https://www.cnblogs.com/wyb666/p/10276317.html
Copyright © 2011-2022 走看看