📜  数组中滑动窗口的中位数|套装2(1)

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

数组中滑动窗口的中位数问题 | 套装2

问题描述

给定一个大小为n的整型数组nums,以及一个大小为k的整型滑动窗口,依次遍历nums中的每个长度为k的窗口,求出窗口中所有数字的中位数。

解决方案
方法一:暴力枚举

对于每个滑动窗口,我们可以暴力将其排序(时间复杂度为$O(k\log{k})$),然后取中间的数(如果窗口大小为奇数)或中间两个数的平均值(如果窗口大小为偶数)作为中位数。这个算法的时间复杂度为$O(nk\log{k})$,其中$n$为数组nums的长度,$k$为窗口大小。

然而,这个算法非常耗时,无法通过本题。因此我们需要尝试更高效的方法。

方法二:双优先队列(实时维护中位数)

我们从方法一中得到的启示是:如果能在$O(\log{k})$的时间内找到当前窗口中的最大数和最小数(即左右区间的中位数),那么就可以得到当前窗口的中位数。

基于这个想法,我们可以使用两个优先队列(堆),一个大根堆维护较小的一半数,一个小根堆维护较大的一半数,使得大根堆的堆顶元素始终小于等于小根堆的堆顶元素。

每当窗口向右移动一位,我们需要将当前窗口的最左边的数从堆中删除,并将最新加入的数插入堆中。这个操作需要保持两个堆的大小以及堆顶元素的约束条件。具体实现可以参考这里

这个算法的时间复杂度为$O(n\log{k})$,其中$n$为数组nums的长度,$k$为窗口大小。相比方法一,它有一个非常大的优势:每次向右移动窗口时,我们只需维护两个大小为$k/2$的堆,而不需要对整个滑动窗口重新排序。因此,这个算法在时间复杂度上比方法一要优秀得多。

代码示例

以下是实现方法二的Python代码示例:

import heapq

class DualHeap:
    def __init__(self, k: int):
        self.small = []
        self.large = []
        self.delayed = {}
        self.k = k
        self.smallSize = 0
        self.largeSize = 0

    def prune(self, heap: List[int]):
        while heap:
            num = heap[0]
            if num in self.delayed:
                heapq.heappop(heap)
                self.delayed[num] -= 1
                if not self.delayed[num]:
                    del self.delayed[num]
            else:
                break

    def makeBalance(self):
        if self.smallSize > self.largeSize + 1:
            self.large.append(-self.small[0])
            heapq.heappop(self.small)
            self.smallSize -= 1
            self.largeSize += 1
            self.prune(self.small)
        elif self.smallSize < self.largeSize:
            self.small.append(-self.large[0])
            heapq.heappop(self.large)
            self.smallSize += 1
            self.largeSize -= 1
            self.prune(self.large)

    def insert(self, num: int):
        if not self.small or num <= -self.small[0]:
            self.small.append(-num)
            self.smallSize += 1
        else:
            self.large.append(num)
            self.largeSize += 1
        self.makeBalance()

    def erase(self, num: int):
        self.delayed[num] = self.delayed.get(num, 0) + 1
        if num <= -self.small[0]:
            self.smallSize -= 1
            if num == -self.small[0]:
                self.prune(self.small)
        else:
            self.largeSize -= 1
            if num == self.large[0]:
                self.prune(self.large)
        self.makeBalance()

    def getMedian(self):
        return -self.small[0] if self.k % 2 == 1 else (self.large[0] - self.small[0]) / 2         

class Solution:
    def medianSlidingWindow(self, nums: List[int], k: int) -> List[float]:
        dh = DualHeap(k)
        for num in nums[:k]:
            dh.insert(num)
        ans = [dh.getMedian()]
        for i in range(k, len(nums)):
            dh.insert(nums[i])
            dh.erase(nums[i-k])
            ans.append(dh.getMedian())
        return ans

注意:上述代码中的DualHeap类是一个用于实现方法二的优先队列(堆)。具体细节可以参考这里