滑动窗口的最大值

题目

思路1

暴力求解法,扫描滑动窗口中的每一个数字,并找出其中的最大值。如果滑动窗口的大小为m,则找到窗口内的最大值需要O(k)时间。对于长度为n的数组,总的时间复杂度为O(mn)

思路2

借助一个辅助队列,从头遍历数组,根据如下规则进行入队列或出队列操作:

  • 如果队列为空,则当前数字入队列
  • 如果当前数字大于队列尾,则删除队列尾,直到当前数字小于等于队列尾,或者队列空,然后当前数字入队列
  • 如果当前数字小于队列尾,则当前数字入队列
  • 如果队列头超出滑动窗口范围,则删除队列头

这样能始终保证队列头为当前的最大值。

以这道题为例,从头开始遍历数组,我们以下标 i 表示遍历到第几个数字:
(1)在开始阶段,队列为空,我们把2入队列,此时i = 0;
在这里插入图片描述
(2)i = 1时,num[1] = 3, 由于3大于队列尾2,所以把2移出队列,把3加入队列;

在这里插入图片描述

(3)i = 2时,num[2] = 4, 由于4大于3, 所以把3移出队列,把4加入队列,此时滑动窗口刚好经过三个元素,滑动窗口内的最大值就是队列的头元素,也就是4;

在这里插入图片描述

(4)i = 3时,num[3] = 2, 因为2小于4,所以把2直接加入队列,此时滑动窗口内的最大值仍然为4;

在这里插入图片描述
(5)i = 4时,num[4] = 6, 6大于2和4,所以把2和4移出队列,把6加入到队列中,此时滑动窗口的最大值为6;
在这里插入图片描述

(6)i = 5时,num[5] = 2, 因为2小于6,所以把2直接加入队列,此时滑动窗口内的最大值仍然为6;
在这里插入图片描述

(7)i = 6时,num[6] = 5, 由于5大于2,所以把2移出队列,把5加入队列中,此时滑动窗口内的最大值仍然为6;

在这里插入图片描述

(8)i = 7时,num[7] = 1, 由于6已经不在滑动窗口中了,所以将6从队列头上删除,此时滑动窗口的最大值为5;
在这里插入图片描述

那么我们是如何判断6是否还在滑动窗口中呢,可以通过数组下标进行判断,我们在队列中存储数组的下标而非数值,这样通过判断下标之间的差值是否大于窗口的大小,就可以判断元素是否还在滑动窗口中。

	/**
	 * 解法1:使用双端队列方法,用LinkedList实现
	 */
	 public ArrayList<Integer> maxInWindows(int[] num, int size) {
        ArrayList<Integer> res = new ArrayList<>();
        if (num == null || num.length == 0 || size == 0 || size > num.length) {
            return res;
        }
        //使用双端队列来实现
        Deque<Integer> deque = new LinkedList<>();
        for (int i = 0; i < num.length; i++) {
            if (!deque.isEmpty()) {
                // 如果队列头元素不在滑动窗口中了,就删除头元素
                if (i >= deque.peek() + size) {
                    deque.pop();
                }

                // 如果当前数字大于队列尾,则删除队列尾,直到当前数字小于等于队列尾,或者队列空
                while (!deque.isEmpty() && num[i] >= num[deque.getLast()]) {
                    deque.removeLast();
                }
            }
            deque.offer(i); // 入队列
            // 滑动窗口经过一个滑动窗口的大小,就获取当前的最大值,也就是队列的头元素
            if (i + 1 >= size) {
                res.add(num[deque.peek()]);
            }
        }
        return res;
	 }

思路3

大顶堆方法:
构建一个窗口size大小的最大堆,每次从堆中取出窗口的最大值,随着窗口往右滑动,需要将堆中不属于窗口的堆顶元素删除。

	/**
	 * 解法2:使用大顶堆,大顶堆只包含三个元素,添加元素num[i]时前先删除num[i-size],
	 * 堆顶元素即为当前窗口的最大值
	 */
	public ArrayList<Integer> maxWindows2(int num[],int size){
		//保存结果
		ArrayList<Integer> result = new ArrayList<>();
		if(size>num.length||size<1) return result;
		//构建大顶堆,因为priorityqueue默认是小顶堆,这里直接用lambda表达式来实现大顶堆
		PriorityQueue<Integer> heap = new PriorityQueue<>((o1,o2) -> o2-o1);
		//先将数组前size个元素入队
		for(int i =0;i<size;i++) {
			heap.offer(num[i]);
		}
		//第一个窗口的最大值
		result.add(heap.peek());
		/*
		 * 接着处理后面的元素(num[0]~num[length-size-1]依次出队,num[size]~num[length-1]依次入队)
		 * 然后每次出队入队后都保存堆顶最大元素
		 */
		for(int i = 0;i+size<num.length;i++) {
			heap.remove(num[i]);
			heap.add(num[i+size]);
			result.add(heap.peek());
		}
		return result;
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值