📜  数组中滑动窗口的中位数(1)

📅  最后修改于: 2023-12-03 15:40:03.038000             🧑  作者: Mango

数组中滑动窗口的中位数

在一些算法问题中,我们需要用到动态的滑动窗口来处理数据。例如在数组中,滑动窗口可以把一些元素聚合在一起并求它们的平均值、最大值或最小值。而在这些问题中,如果我们需要求解滑动窗口的中位数,代码实现起来就会相对复杂。本文将介绍如何使用两个堆来实现动态求解滑动窗口的中位数问题。

方法一:暴力法

暴力求解滑动窗口中的中位数很容易想到,我们直接在每个窗口中取出元素并排序,然后求出中位数即可。但是这种方法显然非常低效,时间复杂度为$O(nklogk)$,其中 n 是数组长度,k 是滑动窗口的大小。

方法二:利用两个堆

在计算数据流中的中位数时,我们可以使用两个堆解决问题。一个大根堆存储数组中较小的一半数字,另一个小根堆存储数组中较大的一半数字。当统计数据流的中位数时,如果两个堆的元素个数相同,则中位数为两个堆顶元素的平均值;否则,中位数为元素个数多的堆的堆顶元素。

我们同样可以使用这个思想来解决滑动窗口中的中位数问题。具体步骤如下:

  1. 在每个窗口中,将元素分为两部分,并用两个堆分别存储较小和较大的部分数字。
  2. 当添加一个元素时,我们需要决定将其添加到较小数字堆还是较大数字堆。我们将新元素与较小数字堆的堆顶元素进行比较,如果新元素比它大,则加入较大数字堆。否则,加入较小数字堆。
  3. 每次移动窗口时,需要从堆中删除移出窗口的数字。同样的,我们需要将删除的数字与较小数字堆的堆顶元素进行比较。如果待删除的数字比堆顶元素大,则从较大数字堆中删除;否则,从较小数字堆中删除。
  4. 在每个窗口结束时,我们都能够得到中位数,根据前面所述的规则求解即可。

下面是 Python 代码实现:

import heapq
from typing import List

class Solution:
    def medianSlidingWindow(self, nums: List[int], k: int) -> List[float]:
        n = len(nums)
        median = lambda small, big: (small[0] - big[0]) / 2.0 if len(small) == len(big) else float(small[0])
        small, big = [], []
        for i in range(n):
            if i >= k:
                if nums[i-k] in small:
                    small.remove(nums[i-k])
                else:
                    big.remove(-nums[i-k])
            if not small or nums[i] <= -small[0]:
                heapq.heappush(small, -nums[i])
            else:
                heapq.heappush(big, nums[i])
            if len(small) > len(big) + 1:
                heapq.heappush(big, -heapq.heappop(small))
            elif len(big) > len(small):
                heapq.heappush(small, -heapq.heappop(big))
            if i >= k - 1:
                res.append(median(small, big))
        return res

其中,我们使用 two-pointers 滑动窗口来控制窗口大小,使用小根堆 big 和大根堆 small 来存储较小数字和较大数字。函数 median 用于计算以两个堆为底的数据流中的中位数。

时间复杂度:$O(nlogk)$,其中 n 是数组长度,k 是窗口大小。在插入元素时,需要在两个堆中插入元素,时间复杂度为 $O(logk)$;移除元素时,同样需要在两个堆中移除元素,时间复杂度也是 $O(logk)$。由于每个元素最多会被插入、移除一次,因此总时间复杂度为 $O(nlogk)$。