From c69a7f02a6a31b073b7ac31aa43f4d613989533c Mon Sep 17 00:00:00 2001 From: "huangge1199@hotmail.com" Date: Sun, 15 Aug 2021 22:49:25 +0800 Subject: [PATCH] =?UTF-8?q?480:=E6=BB=91=E5=8A=A8=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=E4=B8=AD=E4=BD=8D=E6=95=B0(=E4=B8=8D=E6=87=82=E3=80=82?= =?UTF-8?q?=E3=80=82=E3=80=82=E3=80=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor/cn/SlidingWindowMedian.java | 165 ++++++++++++++++++ .../leetcode/editor/cn/SlidingWindowMedian.md | 39 +++++ 2 files changed, 204 insertions(+) create mode 100644 src/main/java/leetcode/editor/cn/SlidingWindowMedian.java create mode 100644 src/main/java/leetcode/editor/cn/SlidingWindowMedian.md diff --git a/src/main/java/leetcode/editor/cn/SlidingWindowMedian.java b/src/main/java/leetcode/editor/cn/SlidingWindowMedian.java new file mode 100644 index 0000000..f1d145e --- /dev/null +++ b/src/main/java/leetcode/editor/cn/SlidingWindowMedian.java @@ -0,0 +1,165 @@ +//中位数是有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。 +// +// 例如: +// +// +// [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 以内的答案将被视作正确答案。 +// +// Related Topics 数组 哈希表 滑动窗口 堆(优先队列) +// 👍 304 👎 0 + +package leetcode.editor.cn; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.PriorityQueue; + +//480:滑动窗口中位数 +class SlidingWindowMedian { + public static void main(String[] args) { + //测试代码 + Solution solution = new SlidingWindowMedian().new Solution(); + } + + //力扣代码 + //leetcode submit region begin(Prohibit modification and deletion) + class Solution { + public double[] medianSlidingWindow(int[] nums, int k) { + DualHeap dh = new DualHeap(k); + for (int i = 0; i < k; ++i) { + dh.insert(nums[i]); + } + double[] ans = new double[nums.length - k + 1]; + ans[0] = dh.getMedian(); + for (int i = k; i < nums.length; ++i) { + dh.insert(nums[i]); + dh.erase(nums[i - k]); + ans[i - k + 1] = dh.getMedian(); + } + return ans; + } + } + + class DualHeap { + // 大根堆,维护较小的一半元素 + private PriorityQueue small; + // 小根堆,维护较大的一半元素 + private PriorityQueue large; + // 哈希表,记录「延迟删除」的元素,key 为元素,value 为需要删除的次数 + private Map delayed; + + private int k; + // small 和 large 当前包含的元素个数,需要扣除被「延迟删除」的元素 + private int smallSize, largeSize; + + public DualHeap(int k) { + this.small = new PriorityQueue<>(Comparator.reverseOrder()); + this.large = new PriorityQueue<>(Comparator.naturalOrder()); + this.delayed = new HashMap(); + this.k = k; + this.smallSize = 0; + this.largeSize = 0; + } + + public double getMedian() { + return (k & 1) == 1 ? small.peek() : ((double) small.peek() + large.peek()) / 2; + } + + public void insert(int num) { + if (small.isEmpty() || num <= small.peek()) { + small.offer(num); + ++smallSize; + } else { + large.offer(num); + ++largeSize; + } + makeBalance(); + } + + public void erase(int num) { + delayed.put(num, delayed.getOrDefault(num, 0) + 1); + if (num <= small.peek()) { + --smallSize; + if (num == small.peek()) { + prune(small); + } + } else { + --largeSize; + if (num == large.peek()) { + prune(large); + } + } + makeBalance(); + } + + // 不断地弹出 heap 的堆顶元素,并且更新哈希表 + private void prune(PriorityQueue heap) { + while (!heap.isEmpty()) { + int num = heap.peek(); + if (delayed.containsKey(num)) { + delayed.put(num, delayed.get(num) - 1); + if (delayed.get(num) == 0) { + delayed.remove(num); + } + heap.poll(); + } else { + break; + } + } + } + + // 调整 small 和 large 中的元素个数,使得二者的元素个数满足要求 + private void makeBalance() { + if (smallSize > largeSize + 1) { + // small 比 large 元素多 2 个 + large.offer(small.poll()); + --smallSize; + ++largeSize; + // small 堆顶元素被移除,需要进行 prune + prune(small); + } else if (smallSize < largeSize) { + // large 比 small 元素多 1 个 + small.offer(large.poll()); + ++smallSize; + --largeSize; + // large 堆顶元素被移除,需要进行 prune + prune(large); + } + } + } +//leetcode submit region end(Prohibit modification and deletion) + +} \ No newline at end of file diff --git a/src/main/java/leetcode/editor/cn/SlidingWindowMedian.md b/src/main/java/leetcode/editor/cn/SlidingWindowMedian.md new file mode 100644 index 0000000..50173f0 --- /dev/null +++ b/src/main/java/leetcode/editor/cn/SlidingWindowMedian.md @@ -0,0 +1,39 @@ +

中位数是有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。

+ +

例如:

+ +
    +
  • [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 以内的答案将被视作正确答案。
  • +
+
Related Topics
  • 数组
  • 哈希表
  • 滑动窗口
  • 堆(优先队列)
  • \n
  • 👍 304
  • 👎 0
  • \ No newline at end of file