滑动窗口问题——生成最大窗口数组

滑动窗口问题——生成最大窗口数组

情景

有一个整型数组 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 int[] first(int[] arr , int w){
        if (arr == null || arr.length == 0 || w == 0 || arr.length < w){
            return new int[0] ;
        }
        int[] result = new int[arr.length - w + 1] ;
        LinkedList<Integer> queue = new LinkedList<>() ;
        for (int i=0 ; i<arr.length ; i++){
            if (queue.isEmpty()){
                //队列为空的时候
                queue.offer(i) ;
            }else if (i - queue.peek() < w){
                //当队列中最后一个结点位置和当前位置相差小于w时,取出来直接使用
                int index = arr[i] > arr[queue.peekLast()] ? i : queue.peekLast() ;
                queue.offer(index) ;
            } else{
                //当队列中最后一个元素位置和当前元素位置相差大于等于w时,暴力扩展
                int maxIndex = i ;
                int time = i - w > -1 ? i - w : -1 ;
                for (int j=i ; j>time ; j--){
                    maxIndex = arr[j] > arr[maxIndex] ? j : maxIndex ;
                }
                queue.offer(maxIndex) ;
            }
        }
        //除去开头的无用窗口
        int k = w - 1 ;
        while(k-- > 0){
            queue.removeFirst() ;
        }
        //为结果数组赋值
        k = queue.size() ;
        for (int i = 0 ; i <k  ; i++){
            result[i] = arr[queue.removeFirst()] ;
        }
        return result ;
    }

第一个版本采用的是队列做法,基于暴力,做了一些小的加速。思想来源于KMPManacher,都是在暴力的基础上做了一些加速。

其实思想很好理解,就是在每一个固定大小的窗口里面取出最大值,然后作为数组返回。

思路:

  • 区别于暴力的一点就是,前一个窗口中最大值的位置和当前位置的差值小于w,可以直接比较两个位置的大小来作为当前窗口最大值的位置。
  • 如果差值大于等于w,则只能选择暴力查找当前窗口的最大值。

第二版

相比较于第一版,做了一个优化,就是把队列去掉了,而是采用一个cache来代替每次队列中最后一个值的作用,其他的都没有改变。

    public int[] second(int[] arr , int w){
        if (arr == null || arr.length == 0 || w == 0 || arr.length < w){
            return new int[0] ;
        }
        //记录每一个窗口的最大值
        int[] next = new int[arr.length] ;
        //缓存着前一个窗口最大值的位置
        int cache = -1 ;
        int x = 0 ;
        for (int i=0 ; i<arr.length ; i++){
            if (cache == -1 ){
                //没有前一个窗口
                cache = i ;
            }else if (i - cache < w){
                //前一个窗口的最大值有效,也就是cache和当前位置的差值小于w
                cache = arr[i] > arr[cache] ? i : cache ;
            } else{
                //前一个窗口的最大值的位置失效了,也就是cache和当前位置的差值大于w
                //开始暴力扩展
                cache = i ;
                //判断一下暴力扩展的范围
                int time = i - w > -1 ? i - w : -1 ;
                for (int j=i ; j>time ; j--){
                    cache = arr[j] > arr[cache] ? j : cache ;
                }
            }
            next[x++] = arr[cache] ;
        }
        //初始化一个结果数组,也就是要返回的数组
        int[] result = new int[arr.length - w + 1] ;
        //将next数组从w-1位置复制到result的0位置,复制长度为result数组的长度
        System.arraycopy(next , w-1 , result , 0 , result.length);
        return result ;
    }

第三版

大喊一声左神牛逼

public static int[] getMaxWindow(int[] arr, int w) {
		if (arr == null || w < 1 || arr.length < w) {
			return null;
		}
    	//采用双端队列
		LinkedList<Integer> qmax = new LinkedList<Integer>();
		//结果数据
    	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[index++] = arr[qmax.peekFirst()];
			}
		}
		return res;
	}

左神在这道题中采用的是双端队列作为窗口,这是解决窗口问题的关键所在。这个双端队列中的元素是arr数组元素的索引。同时双端队列维护的是一个元素值从大到小的索引队列。队列的头相当于我的代码中的cache

滑动窗口问题总结

解决滑动窗口的思路很简单,但是里面的细节成分需要考虑的很多。我的代码就是单一问题的解决,而不是作为一种类型题的解决方案。除了像左神的双端队列,我们也可以采用双指针来解决,其实思路都是一样的。

滑动窗口的重点就是如何控制前后指针的移动(双端队列是就如同前后指针),同时每次移动窗口内的内容将如何变化

饭要少吃,题要多知

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值