【栈与队列】移动窗口生成最大值的数组

功能需求

        有一个整数型数组arr和一个大小为w的窗口从数组的最左端滑到最右端,窗口每次向右滑动一个位置。就像是一个滑动的指针,从头指向尾,然后输出窗口中数据的最大值。

        例如,一组数据为arr[]  = [4,3,5,4,3,3,6,7],窗口大小为w = 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                           窗口中最大值为 5

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

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

        如果数组的长度为n,窗口大小为w,这一共产生n-w-7个窗口的最大值。

    要求:请实现一个函数。

        输入:整型数组arr,窗口大小为w

        输出:一个长度为n-w-1的输入res,res[ i ]表达每一种窗口状态下的最大值。{5,5,5,4,6,7}.

详细解析

        如果数据长度为N,窗口大小为w,如果做出时间复杂度O(N*w)的解法是不能让面试官满意的,本题要求面试者想出的时间复杂度O(N)的实现。所以本题的关键在于利用双端队列来实现窗口最大值的更新。首先生成的是双端队列qmax,qmax中存放着数组arr中的下标。

    假设:遍历到arr[i],qmax的放入规则为:

    1、如果qmax为空,直接把下标i放入qmax,放入过程结束。

    2、如果qmax不为空,取出当前qmax队尾放入队尾存放的下标,假设为j。

        1)如果arr[j] > arr[i],直接把下标i放入qmax的队尾,放入过程结束。

        2)如果arr[j] < arr[i],把j从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的队尾下标为1,又有arr[1] <= arr[2],所以将下标1从qmax的尾部弹出,qmax变成{0}。当前qmax的队尾下标为0,又有arr[0] <= arr[2],所以将下标0从qmax尾部弹出,qmax变成{}。将下标2放入qmax,qmax={2}。此时已经遍历到下标2的位置,窗口arr[0,2]出现,当前qmax对头的下标为2,所以窗口arr[0,2]的最大值为arr[2](即为5)。

       5、遍历到arr[3]==4,当前qmax的队尾下标为2,又有arr[2]>arr[3],所以将下标了放入qmax尾部,qmax={2,3}。 窗口arr[1..3]出现,  当前qmax队头的下标为2,这个下标还没有过期,所以窗口ar[1..3]的最大值为arr[2] (即5)。

        6、遍历到arr[4]== =3,当前qmax的队尾下标为3,  又有arr[3]>arr[4], 所以将下标4放入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的队尾下标为了,又有arr[3]>arr[5],所以将下标5放入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={6}。窗口ar([..]出现,当前qmax队头的下标为6,这个下标没有过期,所以窗口arr[4..6]的 最大值为arr[6](即6)。

        9、 遍历到arr[7]==7, 当前qmax的队尾下标为6,又有arr[6]<=arr[7], 所以将下标6 从qmax的尾部弹出,qmax 变为{}。将下标7放入qmax,qmax={7}。 窗口arr[5..7]出现,当前  qmax队头的下标为7,这个下标没有过期,所以窗口arr[5..7]的最 大值为arr[7](即7)。

        10、依次出现的窗口最大值为[5,5,5,4,6,7],在遍历过程中收集起来,最后返回即可。

具体过程参看如下代码中的getMax Window方法。

代码实现

// 移动窗口获取最大值方法
public int[] getMaxWindow(int[] arr,int w) {
	// 判断数组信息或者截取长度时候合格
	if (arr == null || w < 1 || arr.length < w) {
		// 返回方法
		return;
	}
	// 实例化一个链表结构
	LinkedList<Integer> qmax = new LinkedList<Integer>();
	// 实例化一个int类型数组并且定义长度为执行的次数
	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);
		// 判断对头元素时候为最后一个元素
		if (qmax.peekFirst() == i - w) {
			// 出队第一个元素
			qmax.pollFirst();
		}
		// 如果移动到最后元素前
		if (i >= w - 1) {
			// 把得到的数组存入res集合中
			res[index++] = arr[qmax.peekFirst()];
		}
	}
	// 返回信息的集合
	return res;
}

总结分析

双端队列,将arr中的元素加入res该队列中,若该队列的队尾元素小于等于要加入的元素,则不断的弹出,直到队尾元素大于该元素或者队列为空。此时将该元素的序号加入队列中。同时当 i-w == 队头的序号,则将队头元素弹出。上述过程中,  每个下标值最多进qmax-一次,  出qmax一-次。所以遍历的过程中进出双端队列的操作是时间复杂度为O(N),整体的时间复杂度也为O(N)。写完手工。。睡觉

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章