239. 滑动窗口最大值
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。

返回滑动窗口最大值。

示例:

输入: nums = 
[1,3,-1,-3,5,3,6,7]
, 和 k = 3
输出: 
[3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7
注意:

你可以假设 k 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。

 

解法一(时间复杂度为O(n*logK)):返回窗口中的的最大值,最大最小值我们可以优先考虑到“优先队列”,优先队列用于流式数据的最大最小值。算法题中,有出现slide window的都是高频考点

使用优先队列维持一个大顶堆,(和703 题 数据流中的第k大元素正好相反())。

a.维持一个max heap(删除滑动窗口最左边的元素,加入新的元素)

b.让最大值位于大顶堆

a步骤的维持max head的时间复杂度是logK。

b步骤的时间复杂度是N *O(1),即O(N)。

总体时间复杂度为O(n*logK)。

class Solution {
 
    public int[] maxSlidingWindow(int[] nums, int k) {
               if(nums==null || nums.length<0 || k<=0 || k==1)
            return nums;
 
        queue = new PriorityQueue<>(k, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        
        int[] max = new int[nums.length - k  + 1];
        for (int i = 0; i < nums.length; i++){
//          如果是第K个数之前和第K个数,就说明优先队列没有满,继续添加
            if(i  <  k - 1){
                queue.add(nums[i]);
            }else if(i == k- 1){
                queue.add(nums[i]);
                max[0] = queue.peek();
            }else {
//              优先队列已满,删除滑动窗口最左边的数[i - k],添加新的数
                queue.remove(nums[i - k]);
                queue.add(nums[i]);
                max[i - k + 1] = queue.peek();
            }
        }
 
 
 
        return max;
 
    }
}

 

解法二(时间复杂度为O(N)):
使用双端队列。java中的双端队列deque(支持在两端插入和移除元素)。deque是一个接口,实现它的类有ArrayDeque,LinkedBlockingDeque,LinkedList.

这里参考了一位大神的解法()

我们用双向队列,在遇到新的数的时候,将新的数和双向队列的末尾进行比较,如果末尾的数比新数小,则把末尾的数扔掉,直到该队列的末尾数比新数大或者队列为空的时候才停止。这样,我们可以保证队列里的元素是重头到位的降序。由于队列中只有窗口里的数,就是窗口里的第一大数,第二大数,第三数...。

如何保持队列呢。每当滑动窗口的k已满,想要新进来一个数,就需要把滑动窗口最左边的数移出队列,添加新的数。

我们在添加新的数的时候,就已经移出了一些数,这样队列头部的数不一定是窗口最左边的数。技巧:我们队列中只存储那个数在原数组的下标。这样可以判断该数是否为最滑动窗口的最左边的数。

为什么这个解法的时间复杂度是O(N)呢。因为每个元素在双端队列里有且仅存在过一次。即最多被操作两次,一次是加入该队列的时候,一次是因为后面有更大的数而被移除队列的时候

class Solution {
 
    public int[] maxSlidingWindow(int[] nums, int k) {
    
        if(!(nums instanceof int[]) || nums == null || nums.length == 0)//判断传进来的是否为int数组,int数组是否为空,int数组是否没有数据
            return new int[0];
        
        ArrayDeque<Integer> adq = new ArrayDeque<Integer>(k);
        int[] max = new int[nums.length + 1 - k];//获得该nums数组滑动窗口的个数
        for(int i = 0; i < nums.length; i++){
            //每当新数进来,如果发现队列的头部的数的下标是窗口最左边的下标,则移出队列
            if(!adq.isEmpty() && adq.peekFirst() == i - k)
                adq.removeFirst();
            //把队列尾部的数和新数一一比较,比新数小的都移出队列,直到该队列的末尾数比新数大或者队列为空的时候才停止,保证队列是降序的
            while(!adq.isEmpty() && nums[adq.peekLast()] < nums[i])
                adq.removeLast();
            //从尾部加入新的数
            adq.offerLast(i);
            //队列头部就是该窗口最大的数
            if(i >= k -1)//i < k - 1时,滑动窗口才有最大值
                max[i + 1 -k] = nums[adq.peek()];
                
        }
        return max;
        
        
 
    }
}