Python技术迷

某大厂员工:月供0.44元,在职场上已经没有弱点了。不再是被领导拿捏的对象

刚刷到这个帖子,我第一反应就是,这哥们不是没弱点了,是把命门藏得太深了。月供才0.44元,什么概念,基本等于房贷系统象征性跟你打个招呼,连杯矿泉水都买不了。

Image

你让领导怎么拿捏,画饼?人家不缺这口。加压?大不了准点下班。谈忠诚?他估计心里只会哦一声。

职场里大多数人难受,说白了还是背着点东西,房贷车贷娃的学费,哪样都能被精准戳中。你没有这些包袱,整个人气质都不一样,开会被点名都像在听别人家的事。

领导最怕的就是这种,骂两句没反应,谈前途也没波澜,想靠“你再忍忍”那套往前推,结果人家根本不接。

这种状态看着离谱,其实很现实。手上没链子,谁也别想随便拽着你跑。


算法题:滑动窗口中位数

堆一排数字进窗口里,最烦的不是求最大值,那个单调队列一把梭就完了。中位数不一样,这玩意儿卡人的地方在于:窗口会滑,左边出去一个,右边进来一个,你不仅要知道当前“中间那个数”是谁,还得把已经滑出去的旧值及时清掉。这里我第一眼就不太信“每次排序一次”这种写法,能过小数据,窗口一大基本就开始喘了。写题的时候,先把“插入”和“删除”这两个动作稳住,再谈中位数。整体写法我参考你给的技术文那种从问题直接落地、少铺垫的节奏来处理,但正文和代码都重新写了。

这题比较顺手的做法,是搞两个堆:一个大根堆放左半边,保存较小的一半;一个小根堆放右半边,保存较大的一半。这样一来:

  • 奇数个元素时,中位数就是左堆堆顶;
  • 偶数个元素时,中位数是左右堆顶的平均值。

麻烦在删除。Python 的堆不支持删除任意元素,真去线性删除就慢了。我一般不这么干,直接“延迟删除”:先记账,等这个元素冒到堆顶时再顺手弹掉。

代码别写太花,关键是几个动作分清楚:加元素、删元素、平衡两个堆、取中位数。

import heapq
from collections import defaultdict
from typing import List


classMedianWindow:
def__init__(self):
        self.small = []  # 大根堆,存负数
        self.large = []  # 小根堆
        self.delayed = defaultdict(int)
        self.small_size = 0
        self.large_size = 0

defprune(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)

defbalance(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)

defadd(self, num: int):
ifnot 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()

defremove(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()

defmedian(self, k: int) -> float:
if k % 2 == 1:
return float(-self.small[0])
return (-self.small[0] + self.large[0]) / 2.0


defmedianSlidingWindow(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 当场就露出来了。