生成窗口最大数组

题目

  有一个整型数组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]

思路

  本题的关键在于利用双端队列来实现窗口的最大值更新,首先生成双端队列qmax,qmax中存数组arr的下标。
假设遍历到arr[i],qmax的放入规则为:
1、如果qmax为空,直接把下标为i的放进qmax,放入过程结束。
2、如果qmax不为空,取出队尾存放的下标,假设为j。
  ①、如果arr[j]>arr[i],直接把下标为i放进qmax,放入过程结束。
  ②、如果arr[j]<=arr[i],把j从qmax中弹出,重复qmax的放入规则。
  也就是说,如果qmax为空,就直接放入当前位置;如果qmax不为空,qmax队尾的位置所代表的值如果比当前值大,将一直弹出队尾的位置,直到qmax的位置所代表的的值比当前值大的,当前位置才放入qmax的队尾。

假设遍历到arr[i],qmax的弹出规则为:
  如果当前qmax的队头的下标等于i-w,说明当前qmax队头的下标已经过期,弹出当前队头的下标即可。
  根据如上的放入和弹出规则,qmax便成了一个维护窗口w的子数组的最大值,更新的结构,下面举例说明一下题目给出的例子。
  1、开始时qmax为空,qmax={}
  2、遍历到arr[0] == 4,将下标0放入qmax,qmax=[0]
  3、遍历到arr[1] == 3,当前qmax的队尾下标为0,又有arr[0]>arr[1],所以将下标1放入qmax的尾部,qmax={0,1}
  4.遍历到arr[2] == 5当前qmax的队尾下标为0,又有arr[0]>arr[1],所以将下标1从qmax的尾部弹出,qmax变为{},将下标2放入qmax,qmax={2}。此时已经遍历到下标2的位置,窗口arr[0..2]出现,当前qmax的队头的下标为2,所以出窗口arr[0..2]的最大是arr[2] (即5)
  5、遍历到arr[3] == 4,当前队尾的下标为2,又有arr[3]>arr[4],所以将下标2放入qmax尾部,qmax={2,3}。窗口arr[1..3]出现,当前qmax队头的下标为2,这个下标没有过期,所以窗口arr[1..3]的最大值是arr[2] (即5)
  6、遍历到arr[4] == 3当前队尾的下标为3,又有arr[3]>arr[4],所以将下标2放入qmax尾部,qmax={2,3,4}。窗口arr[2..4]出现,当前qmax队头的下标为2,这个下标没有过期,所以窗口arr[2..4]的最大值是arr[2] (即5)
  7、遍历到arr[5] == 3,当前qmax的队尾下标为4,又有arr[4] <= arr[5],所以将下标4从qmax尾部弹出,qmax变为{2,3}。当前qmax的队尾下标为3,又有arr[3] > arr[5],所以将5这个下标放入qmax队尾,qmax的变为qmax={2,3,5}, 窗口arr[3..5],当前qmax的队头的下标为2,这个下标已经过期,所以qmax的头部弹出,qmax变为{3,5}。当前qmax队头的下标为3,这个下标没有过期,所以窗口arr[3..5]的最大值为arr[3] (即4)
  8、遍历到arr[6] == 6,当前qmax的队尾下标为5,又有arr[5] <= arr[6],所以将下标5从qmax尾部弹出,qmax变为{3}。当前qmax的队尾下标为3,又有arr[3] <= arr[6],所以将下标3从qmax尾部弹出,qmax变为{}。所以将6这个下标放入qmax队尾,qmax的变为qmax={6}, 窗口arr[4..6],当前qmax的队头的下标为6,这个没有过期,所以窗口arr[4..6]的最大值为arr[6] (即6)
  9、遍历到arr[7] == 7,当前qmax的队尾下标为6,又有arr[6] <= arr[7],所以将下标6从qmax尾部弹出,qmax变为{}。所以将7这个下标放入qmax队尾,qmax的变为qmax={7}, 窗口arr[5..7],当前qmax的队头的下标为7,这个没有过期,所以窗口arr[5..7]的最大值为arr[7] (即7)
  10、依次出现的窗口最大值为[5,5,5,4,6,7],在遍历过程中收集起来,最后返回即可。

代码演示

package com.itz.zcy.stackAndQueue;

import java.util.Arrays;
import java.util.LinkedList;

/**
 * 有一个整型数组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]
 */
public class MaxWindows {

    /**
     * 使用LinkedList集合来做双端队列,对队头和队尾元素进行弹出,或者加入队尾
     * @param arr 要求的数组
     * @param w 窗口大小
     * @return
     */
    public static int[] getMaxWindows(int[] arr, int w) {
//        异常判定 进行判空操作
        if (arr == null || arr.length < w) {
            return null;
        }
//        创建一个双端队列,已就是linkedList集合
        LinkedList qmax = new LinkedList<>();
//        创建一个用来存最大值的数组
        int[] res = new int[arr.length - w + 1];
        int index = 0;
        for (int i = 0; i < arr.length; i++) {
//            当队尾下标,所对应的值小于当前的arr[i] 弹出
            while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]) {
                qmax.pollLast();
            }
//            进入队列
            qmax.addLast(i);
//            判定当前的下标是否过期
            if (qmax.peekFirst() == i - w) {
                qmax.pollFirst();
            }
//            从arr数组的w-1个数组res才开始存数,此时窗口才满
            if (i >= w - 1) {
                res[index++] = arr[qmax.peekFirst()];
            }
        }
        return res;
    }

    public static void main(String[] args) {
        int[] arr = {4,3,5,4,3,3,6,7};
        int w = 3;
        int[] maxWindows = getMaxWindows(arr, w);
        System.out.println(Arrays.toString(maxWindows)); //[5, 5, 5, 4, 6, 7]

    }
}

总结

假设数组的长度为N,窗口的大小为w,一般情况我们写的都是O(N x w)的时间复杂度,但是采用看双端队列后,每一个下标最多对qmax进一次,出qamx一次。所以遍历过程双端队列的时间复杂度为O(N),整体的时间复杂度也为O(N)

文献:左程云著 《程序员代码面试指南IT名企算法与数据结构题目最优解》(第二版)
版权声明:此文版权归作者所有!

你可能感兴趣的:(生成窗口最大数组)