中位数是有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。
例如:
[2,3,4],中位数是 3
[2,3],中位数是 (2 + 3) / 2 = 2.5
给你一个数组 nums,有一个长度为 k 的窗口从最左端滑动到最右端。窗口中有 k 个数,每次窗口向右移动 1 位。你的任务是找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。
解法:使用一个大根堆、一个小根堆,将小于中位数的放到大根堆中(使大根堆中全是较小的数),大于中位数的放到小根堆中,并维持两个堆的平衡(k为偶数时两堆相等,为奇数时大根堆比小根堆多一个),这样中位数必然会出自两个堆的堆顶(k为偶数时为两堆顶的平均数,为奇数时为大根堆的堆顶)。
tips: python中模块只能实现小根堆,为了实现大根堆采取了加负号的方法,在使用过程中要注意负号的处理。
class Solution:
def medianSlidingWindow(self, nums: List[int], k: int) -> List[float]:
if k == 1: return nums
import heapq
# 初始化:建立一个大根堆small、一个小根堆big,(大根堆中放的全是比中位数小的数,小根堆中放的全是比中位数大的数)
# 把前k个数放到small中,然后从small中弹出k//2个放到big中,
# 当k为奇数时,small比big多一个,为偶数时相等。
small, big, ans = [], [], []
for i in nums[:k]:
heapq.heappush(small, -i)
for i in range(k // 2):
heapq.heappush(big, -heapq.heappop(small))
# 求中位数
def get():
if k % 2 == 0:
return (-small[0] + big[0]) / 2
else:
return - small[0]
# 对两个堆进行调整,使两个堆大小保持平衡(k为偶数时相等,为奇数时small比big多一个)
def adj():
if len(small) - len(big) > 1: #解题过程中small最多只会比big多两个,所以只处理一次就行了
heapq.heappush(big, -heapq.heappop(small))
while len(big) - len(small) >= 1: #small要等于或比big多一个,所以可能要处理两次
heapq.heappush(small, -heapq.heappop(big))
ans.append(get()) #求第一个中位数
n = len(nums)
for left in range(1, n-k+1):
# 添加进入窗口的元素:如果比大根堆的堆顶还小,说明属于小的部分(大根堆)
if nums[left + k - 1] <= -small[0]:
heapq.heappush(small, -nums[left + k - 1])
else:
heapq.heappush(big, nums[left + k - 1])
adj()
# 将窗口左边的从堆中移出,如果比小根堆的堆顶大,说明属于大的部分。
if nums[left - 1] >= big[0]:
big.remove(nums[left - 1])
heapq.heapify(big)
else:
small.remove(-nums[left - 1])
heapq.heapify(small)
adj()
# 求中位数
ans.append(get())
return ans