友情支持

如果您觉得这个笔记对您有所帮助,看在D瓜哥码这么多字的辛苦上,请友情支持一下,D瓜哥感激不尽,😜

支付宝

微信

有些打赏的朋友希望可以加个好友,欢迎关注D 瓜哥的微信公众号,这样就可以通过公众号的回复直接给我发信息。

wx jikerizhi

公众号的微信号是: jikerizhi因为众所周知的原因,有时图片加载不出来。 如果图片加载不出来可以直接通过搜索微信号来查找我的公众号。

480. 滑动窗口中位数

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

例如:

  • [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 以内的答案将被视作正确答案。

思路分析

利用 295. 数据流的中位数 中的双堆模式在“运动”中,删除,添加,求中位数。

0480 10
  • 一刷

  • 二刷

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
 * 答案是正确的,超大数组测试时,超时了。
 *
 * @author D瓜哥 · https://www.diguage.com
 * @since 2024-08-30 11:45:50
 */
public double[] medianSlidingWindow(int[] nums, int k) {
  // 如果窗口是奇数,则 topSmall 多一个
  Queue<Integer> topSmall = new PriorityQueue<>((a, b) -> Integer.compare(a, b));
  // 使用 Integer.compare,防止两个最小负数相减时溢出
  Queue<Integer> topLarge = new PriorityQueue<>((a, b) -> Integer.compare(b, a));
  double[] result = new double[nums.length - k + 1];
  for (int i = 0; i < k; i++) {
    topSmall.add(nums[i]);
  }
  for (int i = 0; i < k / 2; i++) {
    topLarge.add(topSmall.poll());
  }
  // 先相处再相加,防止溢出
  result[0] = k % 2 == 1 ? 1.0 * topSmall.peek()
    : topSmall.peek() / 2.0 + topLarge.peek() / 2.0;
  for (int i = k; i < nums.length; i++) {
    int num = nums[i];
    if (topSmall.peek() <= num) {
      topSmall.add(num);
    } else {
      topLarge.add(num);
    }

    int delNum = nums[i - k];
    if (topSmall.peek() <= delNum) {
      topSmall.remove(delNum);
    } else {
      topLarge.remove(delNum);
    }
    // 添加,删除,再平衡双堆,这样才能保证堆的平衡性
    // topSmall 应该始终大于等于 topLarge
    while (topLarge.size() > topSmall.size()) {
      topSmall.add(topLarge.poll());
    }
    // topSmall 与 topLarge 的差值不能大于 1
    while (topSmall.size() - topLarge.size() > 1) {
      topLarge.add(topSmall.poll());
    }

    result[i - k + 1] = k % 2 == 1 ? 1.0 * topSmall.peek()
      : topSmall.peek() / 2.0 + topLarge.peek() / 2.0;
  }
  return result;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
 * 优化前,通过 43 / 44 个测试用例。
 *
 * @author D瓜哥 · https://www.diguage.com
 * @since 2025-09-15 22:04:02
 */
public double[] medianSlidingWindow(int[] nums, int k) {
  PriorityQueue<Integer> topSmall = new PriorityQueue<>();
  PriorityQueue<Integer> topLarge = new PriorityQueue<>((a, b) -> Integer.compare(b, a));
  double[] result = new double[nums.length - k + 1];
  int index = 0;
  while (index < nums.length) {
    int num = nums[index];
    if (topLarge.isEmpty() || topLarge.peek() >= num) {
      topLarge.add(num);
    } else {
      topSmall.add(num);
    }

    if (k <= index) {
      int delete = nums[index - k];
      if (delete > topLarge.peek()) {
        topSmall.remove(delete);
      } else {
        topLarge.remove(delete);
      }
    }

    while (topSmall.size() > topLarge.size()) {
      topLarge.offer(topSmall.poll());
    }

    while (topLarge.size() - topSmall.size() > 1) {
      topSmall.offer(topLarge.poll());
    }

    if (topSmall.size() + topLarge.size() == k) {
      result[index - k + 1] = getMedian(k, topSmall, topLarge);
    }
    index++;
  }
  return result;
}

private static double getMedian(int k, PriorityQueue<Integer> topSmall, PriorityQueue<Integer> topLarge) {
  if ((k & 1) == 1) {
    return topLarge.peek();
  }
  return (0D + topSmall.peek() + topLarge.peek()) / 2.0;
}