Python技术迷

hr对我有意思,咋办??

刚看到个贴子,说网友面试小厂,技术面过后,HR突然开始问“你单身吗”“要不要一起吃饭”,他拒绝后担心被挂简历。

Image

我作为程序员第一反应是——这不是技术问题,这是职场边界问题。面试就像写代码,需求明确、接口清晰才能高效运行,突然插入这种“感情需求”,就是强行往代码里塞彩蛋,还可能引发异常。网友里有人说“无所谓,多一个offer少一个没差”,也有人劝“要委婉处理”。我觉得关键在于保持专业态度,拒绝时不用翻脸,但要让对方知道:我来是谈工作,不是谈恋爱。

说到底,职场跟代码一样,逻辑清晰、边界明确,才能稳定运行。守住底线,机会自然还会有的。【备注:文末可领最新资料】

面试题:LFU 缓存

说真话啊,昨天晚上十一点多我在小区地库转圈,车位老是抢不到,脑子里就一直琢磨缓存淘汰这事儿。你们知道吧,平时写接口,Redis 热点还好说,业务侧自己搞个本地缓存,过一会儿就有人问:为啥我放进去的东西被干掉了?我说你用的是 LFU,不常用的就会被“礼貌请出”。他愣住了,哦那个…最少使用频率的缓存对吧,频率低就滚蛋的那个。对,就是它。

先把直觉捋顺:为什么是 LFU

场景很生活化:你家鞋柜就两层,能放 N 双。天天穿的运动鞋肯定不能丢,对吧;那双婚礼穿过一次的皮鞋,基本吃灰。LFU 的意思就是——统计每双鞋被穿的次数,次数最少的先扔出去。它和 LRU(最近最少使用)不太一样,LRU看“最近”,LFU看“次数”。当请求分布很稳定时,LFU更靠谱,因为它能把“常年热门”留住。嗯…别抬杠,抬也行,等我把车停好再说,哈哈。

O(1) 的两个哈希表

脑袋里过一遍就行,别被名词吓到。关键就两个映射:

  • key -> 节点(node):节点里放值、当前频次 freq,以及双向链表指针。
  • freq -> 双向链表:同一频次的键排成一条链,链里再按“最近使用”排一下,这样并列时能淘汰最旧的那一个。

还有个小变量 minfreq,记住当前库里最小的频次,清理时不用全图搜索,直接去 freq[minfreq] 的链尾巴抠一个出来,手起刀落。

操作就两件:

  1. get(key):命中就把这个键从原频次链表摘掉,freq+1 后丢到新链表头。如果原链表空了,而且它正好是 minfreq,那就把 minfreq 加一。
  2. put(key, val):容量满了就去 minfreq 那条链的尾部淘汰一个。新键进来频次从 1 开始,minfreq 置 1。若是更新已有键,就顺手提高频次。

说到这儿我去把窗户关一下…好了继续。

classNode:
    __slots__ = ("key", "val", "freq", "prev", "next")
def__init__(self, key, val, freq=1):
        self.key = key
        self.val = val
        self.freq = freq
        self.prev = None
        self.next = None

classDLinkedList:
def__init__(self):
        self.head = Node(None, None)  # dummy
        self.tail = Node(None, None)  # dummy
        self.head.next = self.tail
        self.tail.prev = self.head
        self.size = 0

defappendleft(self, node):
        node.next = self.head.next
        node.prev = self.head
        self.head.next.prev = node
        self.head.next = node
        self.size += 1

defpop(self, node=None):
if self.size == 0:
returnNone
ifnot node:
            node = self.tail.prev
        node.prev.next = node.next
        node.next.prev = node.prev
        node.prev = node.next = None
        self.size -= 1
return node

def__len__(self):
return self.size

classLFUCache:
def__init__(self, capacity: int):
        self.cap = capacity
        self.size = 0
        self.minfreq = 0
        self.key2node = {}
        self.freq2list = {}

def_touch(self, node: Node):
        freq = node.freq
        lst = self.freq2list[freq]
        lst.pop(node)
if len(lst) == 0:
if self.minfreq == freq:
                self.minfreq += 1
# 可选:节省内存
# del self.freq2list[freq]
        node.freq += 1
        self.freq2list.setdefault(node.freq, DLinkedList()).appendleft(node)

defget(self, key: int) -> int:
if key notin self.key2node or self.cap == 0:
return-1
        node = self.key2node[key]
        self._touch(node)
return node.val

defput(self, key: int, value: int) -> None:
if self.cap == 0:
return
if key in self.key2node:
            node = self.key2node[key]
            node.val = value
            self._touch(node)
return
if self.size == self.cap:
            lst = self.freq2list[self.minfreq]
            evicted = lst.pop()  # 最旧的
del self.key2node[evicted.key]
            self.size -= 1
        node = Node(key, value, 1)
        self.key2node[key] = node
        self.freq2list.setdefault(1, DLinkedList()).appendleft(node)
        self.minfreq = 1
        self.size += 1

# 小试一下
if __name__ == "__main__":
    cache = LFUCache(2)
    cache.put(1, 10)   # {1:10,freq1}
    cache.put(2, 20)   # {1,2}
    cache.get(1)       # 1 的 freq=2
    cache.put(3, 30)   # 淘汰 key=2(freq=1 最旧)
    print(cache.get(2))  # -1
    print(cache.get(3))  # 30
    cache.get(3)         # freq=2
    cache.put(4, 40)     # 淘汰 key=1(freq=2? 不,1 的freq=2但更旧;注意并列走LRU)
    print(cache.get(1), cache.get(3), cache.get(4))  # -1 30 40

有人问并列时咋裁决?我前面说了,链表里按最近使用排序,越靠尾越老,平票就按“谁最久没被碰过”踢谁。还有个坑,容量是 0 的时候,啥也别干,不然面试官会坏笑。至于复杂度,get/put 都是摁着 O(1) 去做的,手法就是哈希表 + 多条双向链表。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领