刚刷到这个帖子,我第一反应就是,这哥们不是没弱点了,是把命门藏得太深了。月供才0.44元,什么概念,基本等于房贷系统象征性跟你打个招呼,连杯矿泉水都买不了。像这样的职场吐槽,在云栈社区开发者广场里也司空见惯。
你让领导怎么拿捏,画饼?人家不缺这口。加压?大不了准点下班。谈忠诚?他估计心里只会哦一声。
职场里大多数人难受,说白了还是背着点东西,房贷车贷娃的学费,哪样都能被精准戳中。你没有这些包袱,整个人气质都不一样,开会被点名都像在听别人家的事。
领导最怕的就是这种,骂两句没反应,谈前途也没波澜,想靠“你再忍忍”那套往前推,结果人家根本不接。
这种状态看着离谱,其实很现实。手上没链子,谁也别想随便拽着你跑。
算法题:滑动窗口中位数
堆一排数字进窗口里,最烦的不是求最大值,那个单调队列一把梭就完了。中位数不一样,这玩意儿卡人的地方在于:窗口会滑,左边出去一个,右边进来一个,你不仅要知道当前“中间那个数”是谁,还得把已经滑出去的旧值及时清掉。这里我第一眼就不太信“每次排序一次”这种写法,能过小数据,窗口一大基本就开始喘了。写题的时候,先把“插入”和“删除”这两个动作稳住,再谈中位数。
这题比较顺手的做法,是搞两个堆:一个大根堆放左半边,保存较小的一半;一个小根堆放右半边,保存较大的一半。这样一来:
- 奇数个元素时,中位数就是左堆堆顶;
- 偶数个元素时,中位数是左右堆顶的平均值。
麻烦在删除。Python 的堆不支持删除任意元素,真去线性删除就慢了。我一般不这么干,直接“延迟删除”:先记账,等这个元素冒到堆顶时再顺手弹掉。
代码别写太花,关键是几个动作分清楚:加元素、删元素、平衡两个堆、取中位数。
import heapq
from collections import defaultdict
from typing import List
class MedianWindow:
def __init__(self):
self.small = [] # 大根堆,存负数
self.large = [] # 小根堆
self.delayed = defaultdict(int)
self.small_size = 0
self.large_size = 0
def prune(self, heap):
while heap:
num = -heap[0] if heap is self.small else heap[0]
if self.delayed[num] == 0:
break
self.delayed[num] -= 1
if self.delayed[num] == 0:
del self.delayed[num]
heapq.heappop(heap)
def balance(self):
if self.small_size > self.large_size + 1:
heapq.heappush(self.large, -heapq.heappop(self.small))
self.small_size -= 1
self.large_size += 1
self.prune(self.small)
elif self.small_size < self.large_size:
heapq.heappush(self.small, -heapq.heappop(self.large))
self.small_size += 1
self.large_size -= 1
self.prune(self.large)
def add(self, num: int):
if not self.small or num <= -self.small[0]:
heapq.heappush(self.small, -num)
self.small_size += 1
else:
heapq.heappush(self.large, num)
self.large_size += 1
self.balance()
def remove(self, num: int):
self.delayed[num] += 1
if num <= -self.small[0]:
self.small_size -= 1
if num == -self.small[0]:
self.prune(self.small)
else:
self.large_size -= 1
if self.large and num == self.large[0]:
self.prune(self.large)
self.balance()
def median(self, k: int) -> float:
if k % 2 == 1:
return float(-self.small[0])
return (-self.small[0] + self.large[0]) / 2.0
def medianSlidingWindow(nums: List[int], k: int) -> List[float]:
mw = MedianWindow()
ans = []
for i, x in enumerate(nums):
mw.add(x)
if i >= k:
mw.remove(nums[i - k])
if i >= k - 1:
ans.append(mw.median(k))
return ans
比如 nums = [1,3,-1,-3,5,3,6,7], k = 3,窗口从左往右滑,中位数依次是 [1,-1,-1,3,5,6]。这个过程里,堆里可能暂时还躺着已经出窗口的旧值,但只要它没跑到堆顶,就先记着;一旦顶上撞见了,再清。这样复杂度能压到 O(n log k),这才像样。
这题真正容易写错的,不是取中位数,而是平衡堆大小的时候把“有效元素个数”和“堆里实际元素个数”混了。只要这两个概念分不开,结果就会一会儿对一会儿错,测到重复值更多的时候尤其明显。写完别急着交,拿 [1,1,1,1]、[1,2,2,3] 这种带重复值的用例先过一遍,很多 bug 当场就露出来了。