疯了,英伟达北京员工,年薪1688万,光个税就扣了687万
刚看到个贴子,说英伟达北京有员工一年总包1688万,个税都交了687万…我是真人都看傻了,这税额在很多城市都能全款买套房了,人家只是“交个税”。
网友们的回帖有感叹不公平的,也有说“人家能力配得上”,我倒觉得两边都有点道理。确实,普通人一年干到掉头发也拿不到别人一张股票的零头,可这薪资背后是超级稀缺岗位、全球竞争、长年高压博弈,真不是随便谁都能拿。
赚钱这事,从来就是价值越稀缺,溢价越离谱。
换个角度想,这种收入水平离我们很远,但看到这种信息,也不必自我打击。你知道它存在,但不影响你脚下继续走。
还是心态放平,见识一下世界的参差,也提醒自己踏实提升能力,能多一点成长就多一点主动权【备注:文末可领最新资料】
面试题:LFU 缓存
我跟你说个事哈,昨天晚上十一点多,我刚准备关电脑刷会儿视频,我们组那个小李突然在群里喊救命,说面试被问了一道 LFU 缓存的题,当场脑子一片空白,让我帮他恶补一下。正好你也问这个,那我就按当时跟他唠嗑的版本,给你从头捋一遍,用 Python 写。 应用 系统内存就那么点儿,对吧,同时只能“宠幸”有限几个应用,多了就得干掉一个。怎么选?最粗暴的是“随机干掉”,稍微聪明点的是 LRU(最近最少使用),就是谁最久没用谁走人。
LFU 呢,全称 Least Frequently Used,意思是:谁被用得最少次数,谁走。次数相同再比谁最久没用,这样看起来就比 LRU 更“公平”一点。
别急着上代码,先把规则讲清楚:
我们这个 LFU 缓存要支持两个操作:
get(key):拿数据put(key, value):塞数据(可能会触发淘汰)
然后得满足几个点:
get和put都要接近 O(1) 时间,不能每次都全表扫描。如果容量满了,再 put新 key,就要踢掉使用频率最低的 key。频率相同时,踢掉最早用的那个(相当于对同一频率再做一层 LRU)。
听起来有点绕是吧?小李当时就说:“哥,这不是要同时记住 value、访问次数、最近访问顺序,脑子不够用了啊……”
我当时在公司楼下抽烟,给他画了个很丑的图,大概就是 3 层结构:
key -> value这层大家都懂key -> freq记录这个 key 被用了多少次freq -> 一堆 key 的有序队列
每个频率下面有一串 key 队列里越靠前的,表示越早被访问(所以需要淘汰的时候,从队头踢人)
用代码表示就是这么几个东西:
self.key2val = {}self.key2freq = {}self.freq2keys = {freq: OrderedDict()}—— 用OrderedDict模拟“按访问时间排好队的 key 列表”self.minFreq记当前最小频率是多少,方便一眼就知道从哪一层删。
关键操作其实就两个词:增频率 和 淘汰。
先说增频率这个事,get(key) 或 put 里更新已有 key 时都要干:
查出这个 key 当前频率 f = key2freq[key]从 freq2keys[f]里把它删掉如果删完后,这个频率下面空了,而且 f == minFreq,那说明最小频率要往上挪一格,minFreq += 1再把它放到 freq2keys[f+1]的队尾(表示刚刚被使用过)更新 key2freq[key] = f + 1
淘汰就简单粗暴点:
当容量满了,又来了一个新 key:
找到当前 minFreq在 freq2keys[minFreq]这个队列里,从队头popitem(last=False),就拿到了一个“最不常用&最久不用”的 key把它从 key2val、key2freq里删掉如果那一层队列空了,可以顺手删掉这个 freq 的节点( del freq2keys[minFreq])
然后新 key 一律从频率 1 开始,minFreq 也重置成 1。
行,说了这么多,该上 Python 代码了,小李就是看完这段才恍然大悟的:
from collections import OrderedDict
classLFUCache:
def__init__(self, capacity: int):
self.capacity = capacity
if capacity <= 0:
# 容量为 0 的时候,直接摆烂,所有操作都无效
return
self.key2val = {} # key -> value
self.key2freq = {} # key -> freq
self.freq2keys = {} # freq -> OrderedDict(key -> None)
self.minFreq = 0
def_increase_freq(self, key: int):
"""内部函数:把 key 的使用次数 +1,并调整在各个表中的位置"""
freq = self.key2freq[key]
# 从旧频率的队列里删掉
keys_at_freq = self.freq2keys[freq]
keys_at_freq.pop(key)
ifnot keys_at_freq: # 这个频率下没人了
del self.freq2keys[freq]
if self.minFreq == freq:
self.minFreq += 1
# 放到新频率的队列末尾
new_freq = freq + 1
self.key2freq[key] = new_freq
if new_freq notin self.freq2keys:
self.freq2keys[new_freq] = OrderedDict()
self.freq2keys[new_freq][key] = None
defget(self, key: int) -> int:
if self.capacity <= 0:
return-1
if key notin self.key2val:
return-1
# 被访问了一次,频率 +1
self._increase_freq(key)
return self.key2val[key]
defput(self, key: int, value: int) -> None:
if self.capacity <= 0:
return
if key in self.key2val:
# 已有 key,更新值 + 提频率
self.key2val[key] = value
self._increase_freq(key)
return
# 容量满了,先淘汰一个
if len(self.key2val) >= self.capacity:
# 找最小频率那一层
keys_at_min = self.freq2keys[self.minFreq]
# OrderedDict 按插入顺序排,last=False 表示从最老的那个 pop
old_key, _ = keys_at_min.popitem(last=False)
ifnot keys_at_min:
del self.freq2keys[self.minFreq]
# 把这个 key 从其他表中删掉
del self.key2val[old_key]
del self.key2freq[old_key]
# 插入新 key,频率从 1 开始
self.key2val[key] = value
self.key2freq[key] = 1
if1notin self.freq2keys:
self.freq2keys[1] = OrderedDict()
self.freq2keys[1][key] = None
self.minFreq = 1
你注意看哈,整个实现里面其实就两个比较绕的点:
一个是 freq2keys这个结构:外层按频率分组,内层用OrderedDict再按时间排序,这样既满足“按次数淘汰”,又兼顾“同次数里按时间淘汰”;另一个是 minFreq的维护:只要某个频率的桶空了,而且它刚好是当前最小频率,就让它往上加一,这样你随时都知道“从哪一层开始踢人”。
其余的都是搬砖:增频率的时候从一个桶挪到另一个桶,满了就从最小频率的队头踢掉一个。
反正小李看完之后,第二天又去面了同一家,面试官刚说到 LFU,他直接一顿嘴炮加伪代码,整个人挺自信的。你要是再多敲几遍这段代码,顺嘴能说出“key2val、key2freq、freq2keys、minFreq 各自干嘛的”,基本这题就稳了。
行了我先去喝口水,你要是想顺带再聊聊和 LRU 的对比或者怎么用双向链表手撸一版,不用 OrderedDict 的那种,也可以继续问。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB