239. Sliding Window Maximum

输入:一个int数组nums,int k = 窗口大小
输出:每个窗口内的最大值
规则:每次向右移动一个位置。每次窗口内可以看到k个数。将算法优化到O(n)。
例如:输入nums = [1,3,-1,-3,5,3,6,7], and k = 3,输出:[3,3,5,5,6,7]
暴力分析:遍历每个窗口内的数找到最大值。有n-k+1个窗口。算法复杂度O(nk)。

	public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        if (n * k == 0) return new int[0];
        
        int [] output = new int[n - k + 1];
        for (int i = 0; i < n - k + 1; i++) {
            int max = Integer.MIN_VALUE;
            for(int j = i; j < i + k; j++) 
                max = Math.max(max, nums[j]);
            output[i] = max;
        }
        return output;
    }

使用队列:我们知道进一步的优化就是使用最大堆,将数值比较的部分由O(k)变为O(logk)。但仍然不满足要求。可以使用一个双端队列deQue,存放数组下标。
deQue满足两个条件:1 队列内的元素都是窗口内的元素下标;2 队列头部是数值最大的元素下标。
例如:nums = [1,3,-1,-3,5,3,6,7], and k = 3,output是返回值
i=0,队列:0
i=1,队列:1(先从对头删除不是窗口内的元素,其次从队尾开始比较删除比nums[i]小的元素)
i=2,队列:1->2,output[0] = nums[1]=3
i=3,队列:1->3,output[1] = nums[1] =3
i=4,队列:先从对头删除不是窗口内的元素,1被删除;其次从队尾开始比较删除比nums[4]小的元素:队列为空;插入元素4。最终队列:4,output[2] =nums[4]=5.
i=5,队列:4->5,output[3] =nums[4]=5.
i=6,队列:6,output[4] =nums[6]=6.
i=7,队列:7,output[5] =nums[7]=7.

	private int[] nums;
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums == null || nums.length==0) return new int[]{};
        this.nums = nums;
        int n = nums.length;
        if(n*k == 0) return new int[]{0};
        Deque<Integer> deQue = new ArrayDeque<Integer>();
        for(int i=0;i<k;i++){
            cleanDque(i,k,deQue);
            deQue.offerLast(i);

        }
        int[] maxs = new int[n-k+1];
        for(int i=k;i<n;i++){
            maxs[i-k] = nums[deQue.peekFirst()];
            cleanDque(i,k,deQue);
            deQue.offerLast(i);
        }
        maxs[n-k] = nums[deQue.peekFirst()];
        return maxs;
    }

    private void cleanDque(int i,int k,Deque<Integer> deQue){
        if(!deQue.isEmpty() && deQue.getFirst() == i-k){
            deQue.pollFirst();
        }
        while(!deQue.isEmpty() && nums[deQue.peekLast()] < nums[i]){
            deQue.pollLast();
        }
    }

动态规划分析:直觉上,已知子数组0到2的最大值,应该是能推出子数组1到3的最大值。(没有解决方法)
我们可以将数组按照k分为n/k或者n/k+1个小数组。
[1,3,-1] [-3,5,3] [6,7]
在滑动窗口中,一个窗口可能在一个小数组内:例如[1,3,-1],也可能跨越两个子数组:例如 3,-1] [-3。
第一种情况比较简单,建立数组left,left[j]表示从子数组开始到下标j最大的元素,方向左->右。left = {1,3,3, -3,5,5,6,7}
第二种情况比较复杂。我们先建一个数组right,right[j]表示从子数组开始到下表j的最大元素,方向右->左。right数组变化情况:right[7] = 7,right[6]=7,right[5]=3,right[4]=5,right[3]=5,right[2]=-1,right[1]=3,right[0]=3
当要找窗口3,-1] [-3,也就是下标从1到4窗口最大值的时候,right[1]是nums[1],nums[2]的最大值,left[3]是nums[3]的最大值。用一个线段来看right[1]和left[3]的最大值就是窗口内的最大值。所以窗口(1,4)的最大值=max(right[1],left[5])。

	public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums == null || nums.length==0) return new int[]{};
        if(k==1) return nums;
        int n = nums.length;
        int[] left = new int[n];
        int[] right = new int[n];
        left[0] = nums[0];
        for(int i=1;i<n;i++){
            if(i%k==0){
                left[i] = nums[i];
            }else{
                left[i] = Math.max(nums[i],left[i-1]);
            }
        }

        right[n-1] = nums[n-1];
        for(int i=n-2;i>=0;i--){
            if((i+1)%k==0){
                right[i] = nums[i];
            }else{
                right[i] = Math.max(right[i+1],nums[i]);
            }
        }

        int[] output = new int[n-k+1];
        for(int i=0;i<n-k+1;i++){
            output[i] = Math.max(right[i],left[i+k-1]);
        }
        return output;
    }

参考链接:url

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值