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,线程不安全。