滑动窗口求解最大 最小值问题

结论:

  1. 求最大值时使用双端对维护递减数据,即队列中数据依次递减,队列头部数据始终为最大值,每次将遍历到的数据与队列尾部的数据进行比较
    如果不违反队列的递减规律(遍历到的数据元素小于队列尾部数据)就直接插入队列尾部
    如果违反了队列的递减规律就依次从队列尾部弹出数据,直到找到能够保持队列递减规律的位置。
  2.  求最小值时使用双端对维护递增数据,即队列中数据依次递增,队列头部数据始终为最小值,每次将遍历到的数据与队列尾部的数据进行比较
    如果不违反队列的递减少规律(遍历到的数据元素小于队列尾部数据)就直接插入队列尾部
    如果违反了队列的递增规律,就依次从队列尾部弹出数据,直到找到能够保持队列递增规律的位置。
  3. 求最大值时,滑动窗口新进来的数据arr[i] ,窗口内的数据最大值一定是 ≥ arr[i] ,所以要弹出队列内小于 arr[i] 的数据,且窗口在包含的 arr[i] 区域内移动时,最大值也一定 ≥ arr[i],同理求最小值。
  4. 求最大值时,如果  arr[i] 比 队列尾部数据要小,直接加入到队列尾部,因为数组 arr[i] 之后如果一直在减少,arr[i] 就有可能会成为最大值,所以要将 arr[i] 加入到队列尾部。
  5. 时间复杂度为O(N)

题目:
有一个整型数组 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}
 

解题思路:

窗口滑动过程中如果最大值位于窗口的最末尾,移除的是窗口的最大值,就需要找到新的最大值,所以可以用一个数组或队列记录下窗口内已排好续的数组索引,每次滑动窗口时比较是否是移除了最尾部的最大值。

可以用双端队列来优化,始终将最大值到队头,如果最大值移除了,就轮循队列,找到新的最大值。

使用了一个队列保存将窗口内的值的索引,且将索引对应的数组值进行由大到小的排序,队列头部放最大值索引,队列尾部放小值索引(队列中数据对应的数组值是由大到小的排序)每次新来一个值时,与队列尾部的数据进行比较,如果比尾部的数据大,就将尾部数据弹出,在比较尾部的下一个数据,直到找小于队列尾部数据的值或者队列为空的位置将新的arr[i]的索引插入到队列,之所以要弹出小于arr[i]的值,是因为在当前的窗口内最大值一定是  ≥ arr[i],并且在接下来的包含arr[i] 的 (w-1)个 窗口内,最大值也一定是 ≥ arr[i],所以没有必要保留小于arr[i]的值,因为队列内部的数据是数组的索引 且是由小到大的,注意队列保存的不是数组的具体值而是数组的索引,每次添加完成后需要校验队列头部的数据即最大值的索引是否在 当前的窗口范围内,如果不在了,需要从队头弹出不在窗口内的数组索引。

假设遍历到 arr[i],qmax 的放入规则为:
.如果 qmax 为空,直接把下标 i 放进 qmax,放入过程结束。
.如果 qmax 不为空,取出当前 qmax 队尾存放的下标,假设为 j。
 1)如果 arr[j]>arr[i],直接把下标 i 放进 qmax 的队尾,放入过程结束。
 2)如果 arr[j]<=arr[i],把 j 从 qmax 中弹出,重复 qmax 的放入规则。

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

	/** 
	 * 窗口滑动走掉的值是最大值时就得用新添加的值,与旧得窗口值比较找到最大得值
	 * 可以用双端队列来优化,始终将最大值到队头,如果最大值移除了,就轮循队列,找到新的最大值
	 * 
	 * 假设遍历到 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 的放入规则。
	 */
	public static int[] getRes(int[] arr, int w) {
		int[] res = new int[arr.length - w + 1];
		// 使用双端队列
		LinkedList<Integer> maxIndexQueue = new LinkedList<>();
		for (int i = 0; i < arr.length; i++) {
			while(true) {
				if (maxIndexQueue.isEmpty() || arr[maxIndexQueue.peekLast()] > arr[i]) {
					maxIndexQueue.add(i);
					break;
				} else{
					maxIndexQueue.removeLast();
				}
			}
			if (maxIndexQueue.peekFirst() == i-w ) {
				maxIndexQueue.removeFirst();
			}
			if (i+1>=w) res[i+1-w] = arr[maxIndexQueue.peekFirst()];
		}
		return res;
	}
	
	/**
	 * 优化中间的while循环,优化思路就是取if的反值
	 */
	public static int[] getRes2(int[] arr, int w) {
		int[] res = new int[arr.length - w + 1];
		// 使用双端队列  队头维护最大值  队列内数据依次递减
		LinkedList<Integer> maxIndexQueue = new LinkedList<>();
		for (int i = 0; i < arr.length; i++) {
           // 队列内数据递减的,新的数据不违反递减规律就直接加入到队列中
			while (!maxIndexQueue.isEmpty() && arr[maxIndexQueue.peekLast()] > arr[i]) {
				// while 循环是删除比当前值要小的值,因为在包含arr[i] 的滑动窗口内  最大值一定 ≥ arr[i]
                maxIndexQueue.removeLast();
			}
                     // 如果arr[i] 小于最末尾值值时,直接加入队列,
			// 数组 arr[i]之后的数据一直在递减,某一时刻 arr[i]就会成为最大值
			maxIndexQueue.add(i);
			if (maxIndexQueue.peekFirst() == i-w ) {
				maxIndexQueue.removeFirst();
			}
			if (i+1>=w) res[i+1-w] = arr[maxIndexQueue.peekFirst()];
		}
		return res;
	}
	


        /**
	 * 滑动窗口最小值
	 */
	public static int[] getMin(int[] arr, int w){
		int[] result = new int[arr.length - w +1];
		// 使用双端队列  队头维护最小值,队列数据依次递增
		LinkedList<Integer> minque = new LinkedList<>();
		
		for (int i = 0; i < arr.length; i++) {
			// 队列内数据递增的,新的数据不违反递增规律就直接加入到队列中
			while(!minque.isEmpty() && arr[i]<= arr[minque.getLast()] ){
				minque.removeLast();
			}
			minque.addLast(i);

			if (minque.size() > w) minque.removeFirst();
			if (i + 1 >= w) {
				result[i + 1 - w] = arr[minque.getFirst()];
			}
		}
		return result;
	}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值