同事发了18万年终奖,领导只收到6万,以为是发错了,去找总监核实,结果总监说:你作为领导,绩效不理想。但你下属绩效好,这很正常。
真给我看乐了。
一个领导看到下属年终奖拿了18万,自己才6万,第一反应不是恭喜团队牛,而是怀疑财务手抖发错了。跑去找总监确认,结果人家一句话给他按椅子上了:你带的人表现好,不代表你自己绩效好。
很多领导平时最爱说“团队成绩就是我的成绩”,下属加班他领功,下属背锅他隐身。可真到分钱的时候,公司突然开始算细账了:项目是谁扛的,活是谁干的,关键问题是谁解决的,一扒拉全清楚。
这事最尴尬的地方在于,下属拿得多,说明团队没废;领导拿得少,说明公司也没瞎。
估计他走出总监办公室那一刻,脑子里全是问号:原来领导也不是自动高配啊。
考场就座这个题,最容易写歪的地方,不是 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() 会很舒服,不用遍历。
这个题写到最后,其实不是考堆本身,而是考你有没有把“座位变化”抽象成“区间变化”。
坐下:一个区间拆成两个。
离开:两个区间合成一个。
把这两件事维护清楚,剩下的就是堆顶取最优。这个题就没那么吓人了。