Python技术迷

同事发了18万年终奖,领导只收到6万,以为是发错了,去找总监核实,结果总监说:你作为领导,绩效不理想。但你下属绩效好,这很正常。

真给我看乐了。

一个领导看到下属年终奖拿了18万,自己才6万,第一反应不是恭喜团队牛,而是怀疑财务手抖发错了。跑去找总监确认,结果人家一句话给他按椅子上了:你带的人表现好,不代表你自己绩效好。

Image

很多领导平时最爱说“团队成绩就是我的成绩”,下属加班他领功,下属背锅他隐身。可真到分钱的时候,公司突然开始算细账了:项目是谁扛的,活是谁干的,关键问题是谁解决的,一扒拉全清楚。

这事最尴尬的地方在于,下属拿得多,说明团队没废;领导拿得少,说明公司也没瞎。

估计他走出总监办公室那一刻,脑子里全是问号:原来领导也不是自动高配啊。

算法题:考场就座

考场就座这个题,最容易写歪的地方,不是 seat(),而是 leave()。

一开始我也会下意识想:来一个学生就扫一遍空座位,找离最近人的最大距离。这个思路小数据能过,数据一大就难看了。因为每次 seat() 都全量扫描,等于把问题写成了“每次重新排考场”。

这题真正要维护的不是座位,而是“空区间”。

比如现在有人坐在:

0       4       9

空区间大概就是:

(0,4)  (4,9)

下一个人应该坐哪?不是随便找中点,而是找“坐进去以后,离最近的人最远”的那个区间。

这里有三个边界要单独看:

左边界:(-1, r)     坐 0
右边界:(l, n)      坐 n - 1
中间段:(l, r)      坐 (l + r) // 2

我一般看到这种“每次取最优区间,又要删除、合并区间”的题,第一反应就是堆。但 Python 的堆有个毛病:它不方便删除堆中间的元素。所以这里别硬删,做懒删除。

代码可以这么写:

import heapq


classExamRoom:

def__init__(self, n: int):
        self.n = n
        self.heap = []
        self.left = {}
        self.right = {}
        self._add(-1, n)

def_score(self, l: int, r: int) -> int:
if l == -1:
return r
if r == self.n:
return self.n - 1 - l
return (r - l) // 2

def_add(self, l: int, r: int) -> None:
if r - l <= 1:
return
        self.left[r] = l
        self.right[l] = r
        heapq.heappush(self.heap, (-self._score(l, r), l, r))

def_remove(self, l: int, r: int) -> None:
        self.left.pop(r, None)
        self.right.pop(l, None)

defseat(self) -> int:
while self.heap:
            _, l, r = heapq.heappop(self.heap)
if self.right.get(l) == r and self.left.get(r) == l:
break

if l == -1:
            pos = 0
elif r == self.n:
            pos = self.n - 1
else:
            pos = (l + r) // 2

        self._remove(l, r)
        self._add(l, pos)
        self._add(pos, r)
return pos

defleave(self, p: int) -> None:
        l = self.left[p]
        r = self.right[p]
        self._remove(l, p)
        self._remove(p, r)
        self._add(l, r)

这里有个细节别忽略:堆里放的是 (-距离, l, r)。

为什么要带 l?因为距离相同的时候,题目要求坐编号更小的位置。Python 的堆会继续比较后面的字段,l 小的区间自然排前面,这个顺手就把规则处理掉了。

leave(p) 也不是把 p 标记为空这么简单。它要找到 p 左边的区间端点和右边的区间端点,然后把两个小区间合并成一个大区间。

比如:

(0,4) + (4,9)  ->  (0,9)

所以代码里用了两个表:

self.left[r] = l
self.right[l] = r

一个根据右端点找左端点,一个根据左端点找右端点。这个设计看着有点绕,但 leave() 会很舒服,不用遍历。

这个题写到最后,其实不是考堆本身,而是考你有没有把“座位变化”抽象成“区间变化”。

坐下:一个区间拆成两个。

离开:两个区间合成一个。

把这两件事维护清楚,剩下的就是堆顶取最优。这个题就没那么吓人了。