leetcode:239.滑动窗口最大值

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

返回滑动窗口中的最大值。

示例 1:

输入: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

刚看到这个题的时候,想到是用TreeMap来排序,于是就用这个来实现。

TreeMap的key保存数组的值,value保存key的出现次数,left移动时value-1,如果value=0,则删除这个key。

 1 class Solution {
 2     public int[] maxSlidingWindow(int[] nums, int k) {
 3         
 4         //用于保存结果
 5         int[] arr = new int[nums.length-k+1];
 6         int left=0,right=0;
 7         //有序的值以及值出现的次数
 8         Map<Integer,Integer> treeMap = new TreeMap<>();
 9 
10         while (right<nums.length){
11             //先将前面k-1个数,及其出现次数放入排序map
12             while(right<k-1){
13                 treeMap.put(nums[right],treeMap.getOrDefault(nums[right],0)+1);
14                 right++;
15             }
16             //将right值加入treeMap
17             treeMap.put(nums[right],treeMap.getOrDefault(nums[right],0)+1);
18             right++;
19             //保存最大值
20             arr[left] = ((TreeMap<Integer, Integer>) treeMap).lastKey();
21             //删除left值
22             treeMap.put(nums[left],treeMap.get(nums[left])-1);
23             if(treeMap.get(nums[left])==0) treeMap.remove(nums[left]);
24             left++;
25         }
26         return arr;
27     }
28 }

时间复杂度:O(nlogn),其中 n是数组长度。向有序集合中添加或删除元素都是O(logn) 的时间复杂度。每个元素最多被添加与删除一次。

空间复杂度:O(k),TreeMap中始终不超过k个数。

这里说说TreeMap:顾名思义,树结构,其实底层是红黑树:https://blog.csdn.net/qq_36610462/article/details/83277524

1.如果没有自定义comparator,则按照key自然排序。

2.所有操作的时间复杂度都是O(logn)。

3.put()方法:就是把key小的放在父节点的left子节点,大的放在right子节点,然后通过fixAfterInsertion()方法把二叉树转变红黑树结构。

4.fail-fast,线程不安全。

5.TreeMap常被用于java实现的一致性哈希算法。

 

官方方法一:优先队列(PriorityQueue)。

为了方便判断堆顶元素与滑动窗口的位置关系,我们可以在优先队列中存储二元组 (num,index),表示元素num 在数组中的下标为index。

窗口滑动时,就可以移除left元素。

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] pair1, int[] pair2) {
                return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
            }
        });
        for (int i = 0; i < k; ++i) {
            pq.offer(new int[]{nums[i], i});
        }
        int[] ans = new int[n - k + 1];
        ans[0] = pq.peek()[0];
        for (int i = k; i < n; ++i) {
            pq.offer(new int[]{nums[i], i});
            while (pq.peek()[1] <= i - k) {
                pq.poll();
            }
            ans[i - k + 1] = pq.peek()[0];
        }
        return ans;
    }
}

时间复杂度:O(nlogn),其中n是数组nums 的长度。在最坏情况下,数组nums 中的元素单调递增,那么最终优先队列中包含了所有元素,没有元素被移除。由于将一个元素放入优先队列的时间复杂度为O(logn),因此总时间复杂度为 O(nlogn)。

空间复杂度:O(n),即为优先队列需要使用的空间。这里所有的空间复杂度分析都不考虑返回的答案需要的O(n) 空间,只计算额外的空间使用。

如果对这些数据结构都熟练的话,找最大值或者最小值,首先想到的肯定是用优先队列,也就是二叉堆,因为这个天生就是找最大最小值的。

这里来简单说一下优先队列(PriorityQueue)。

1.底层数据结构是二叉堆:二叉堆是一个完全二叉树,根节点总是大于左右子节点(大顶堆),或者是小于左右子节点(小顶堆),根节点即为堆顶。

2.二叉堆的详细理解:https://blog.csdn.net/xiao__jia__jia/article/details/82755722

3.如果没有自定义comparator,则按照key自然排序。

4.遵循先进先出原则,优先队列的大小是不受限制的,但在创建时可以指定初始大小。当我们向优先队列增加元素的时候,队列大小会自动增加。

5.fail-fast,线程不安全。

 

posted @ 2021-04-20 16:15  蜗壳吃虾米  阅读(103)  评论(0)    收藏  举报