滑动窗口-滑动窗口中位数
2020-01-10 16:16:41
问题描述:
中位数是有序序列最中间的那个数。如果序列的大小是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。
例如:
[2,3,4],中位数是 3
[2,3],中位数是 (2 + 3) / 2 = 2.5
给出一个数组 nums,有一个大小为 k 的窗口从最左端滑动到最右端。窗口中有 k 个数,每次窗口向右移动 1 位。你的任务是找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。
示例:
给出 nums = [1,3,-1,-3,5,3,6,7],以及 k = 3。
窗口位置 中位数
--------------- -----
[1 3 -1] -3 5 3 6 7 1
1 [3 -1 -3] 5 3 6 7 -1
1 3 [-1 -3 5] 3 6 7 -1
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] 6
因此,返回该滑动窗口的中位数数组 [1,-1,-1,3,5,6]。
提示:
你可以假设 k 始终有效,即:k 始终小于输入的非空数组的元素个数。
与真实值误差在 10 ^ -5 以内的答案将被视作正确答案。
问题求解:
解法一:双pq
注意!Java中比较Integer务必使用compareTo,直接相减会出错。
public double[] medianSlidingWindow(int[] nums, int k) { int n = nums.length; double[] res = new double[n - k + 1]; // 排序后的右侧一半数据 PriorityQueue<Integer> minheap = new PriorityQueue<Integer>((Integer o1, Integer o2) -> o1.compareTo(o2)); // 排序后的左侧一半数据 PriorityQueue<Integer> maxheap = new PriorityQueue<Integer>((Integer o1, Integer o2) -> o2.compareTo(o1)); for (int i = 0; i < n; i++) { int num = nums[i]; if (maxheap.isEmpty() || num <= maxheap.peek()) maxheap.add(num); else minheap.add(num); if (i >= k) { int remove = nums[i - k]; if (maxheap.contains(remove)) maxheap.remove(remove); else minheap.remove(remove); } while (minheap.size() > maxheap.size()) maxheap.add(minheap.poll()); while (maxheap.size() > minheap.size() + 1) minheap.add(maxheap.poll()); if (i >= k - 1) { if (k % 2 != 0) res[i - k + 1] = Double.valueOf(maxheap.peek()); else res[i - k + 1] = 0.5 * minheap.peek() + 0.5 * maxheap.peek(); } } return res; }
解法二:插入排序
leetcode上有一条非常类似的题目,叫sliding window max,那一题可以使用单调队列高效的进行求解。
本题和上述的题目还是有一点区别的,首先最优的时间复杂度不是O(n),而是O(nlogn),如果要达到O(nlogn)需要自定义bst这个是非常耗时的。有同学使用两个优先队列来进行求解,但是在这个过程中需要维护优先队列,尤其是删除队列中的元素,这个在实现的时候是O(n)的时间复杂度,因此使用两个优先队列并不能很好的降低时间复杂度。
如何简洁高效的解决问题才是关键。
首先对于获得中位数,显然需要对数字进行排序。如果每次都重新排序,则每个窗口都需要O(klogk)的时间复杂度,这个是难以接受的。
事实上,考虑到每次滑动窗口已然有序的事实,如果采用插入排序的思路,那么每次维护排序的性质,就只需要O(n)的时间复杂度,那么和使用两个优先队列的时间复杂度就是一样的了,并且在实际实现的时候要简单和容易很多。
下面给出使用insert sort的算法来解决该问题的算法,实际的算法表现还是非常不错的。
public double[] medianSlidingWindow(int[] nums, int k) { int n = nums.length; double[] res = new double[n - k + 1]; int[] sorted = new int[k]; for (int i = 0; i < k; i++) sorted[i] = nums[i]; Arrays.sort(sorted); res[0] = sorted[(k - 1) / 2] * 0.5 + sorted[k / 2] * 0.5; for (int i = k; i < n; i++) { delete(sorted, nums[i - k]); add(sorted, nums[i]); res[i - k + 1] = sorted[(k - 1) / 2] * 0.5 + sorted[k / 2] * 0.5; } return res; } private void delete(int[] nums, int num) { int n = nums.length; int idx = 0; for (; idx < n; idx++) { if (nums[idx] == num) break; } for (int i = idx + 1; i < n; i++) { nums[i - 1] = nums[i]; } } private void add(int[] nums, int num) { int n = nums.length; int idx = n - 2; for (; idx >= 0; idx--) { if (nums[idx] > num) nums[idx + 1] = nums[idx]; else { nums[idx + 1] = num; break; } } if (idx == -1) nums[0] = num; }