LFU 缓存,也就是 LFU Cache,最近写的比较多,就在此记录一下。之前华为面试的时候也遇到了这个题,还好写过。LFU 缓存的定义就不用多说了吧,就是删除最近使用频率最少的一项,如果使用频率一样,就按照最近使用时间来删。之前实现 LFU Cache 写了一大堆代码,最近发现有比较简单的实现方法,测试了一下是没有什么问题,但是还是先不分享了,下面主要转载记录一下 LeetCode 上面的 LFUCache 这个类的实现方法,代码是基于 Python 3 的。
LFU Cache 实现起来比较复杂,最常见的是用二级链表。最近发现似乎可以直接用 pqdict 这个库来实现,并且目前也已经实现了,测试了一下并没有什么 bug。
下面分享转载一下 LeetCode 上面的 LFU Cache 问题,并转载上面最热门的一个题解,仅作记录。
文章目录
隐藏
一、问题描述
请你为最不经常使用(LFU)缓存算法设计并实现数据结构。
实现 LFUCache
类:
LFUCache(int capacity)
– 用数据结构的容量capacity
初始化对象int get(int key)
– 如果键存在于缓存中,则获取键的值,否则返回 -1。void put(int key, int value)
– 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最久未使用 的键。
注意「项的使用次数」就是自插入该项以来对其调用 get
和 put
函数的次数之和。使用次数会在对应项被移除后置为 0。
进阶:
你是否可以在 O(1) 时间复杂度内执行两项操作?
二、输入输出示例
示例: 输入: ["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"] [[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]] 输出: [null, null, null, 1, null, -1, 3, null, -1, 3, 4] 解释: LFUCache lFUCache = new LFUCache(2); lFUCache.put(1, 1); lFUCache.put(2, 2); lFUCache.get(1); // 返回 1 lFUCache.put(3, 3); // 去除键 2 lFUCache.get(2); // 返回 -1(未找到) lFUCache.get(3); // 返回 3 lFUCache.put(4, 4); // 去除键 1 lFUCache.get(1); // 返回 -1(未找到) lFUCache.get(3); // 返回 3 lFUCache.get(4); // 返回 4
- 来源:力扣(LeetCode)
- 链接:https://leetcode-cn.com/problems/lfu-cache
- 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
三、LFUCache
class LFUCache: # 用于记录最后使用时间的链表 class Node: def __init__(self, key: int): self.prev = None self.next = None self.key = key def add(self, node): self.next = node node.prev = self def delete_self(self): if self.next is not None: self.next.prev = self.prev if self.prev is not None: self.prev.next = self.next # 用于记录使用次数的链表 class Layer: def __init__(self, depth: int): self.prev = None self.next = None # depth表示使用次数 self.depth = depth # 这个就是二级的hashmap 加上下面的二级链表 做成了LRU的缓存 self.queue = {} # 二级链表头 self.start = LFUCache.Node(-1) # 二级链表尾巴 self.end = self.start def __init__(self, capacity: int): self.capacity = capacity # 这个就是一级的hashmap 加上下面的链表 做成了LFU的缓存 self.data = {} # 这个map用于指向链表结点,如果重新设计的话,可以和上面的map合并的 self.kToLayer = {} # 链表尾巴 self.end_layer = None # 链表头 self.top_layer = self.Layer(0) def get(self, key: int) -> int: r = self.data.get(key, -1) if r != -1: # 更新记录使用次数的链表 self.kToLayer[key] = self.update_layer(self.kToLayer[key], key) return r def put(self, key: int, value: int) -> None: # 1. capacity为0的话直接不干了 if self.capacity == 0: return # 2. 数据已经存在的话,就更新记录使用次数的链表 if self.data.get(key) is not None: self.data[key] = value self.kToLayer[key] = self.update_layer(self.kToLayer[key], key) return # 3.1 如果超过了限度,就从链表头取出一个,删掉 if len(self.data) >= self.capacity: key_to_del = self.pop_queue_from_layer(self.top_layer) self.data.pop(key_to_del) now = self.kToLayer[key_to_del] if len(now.queue) == 0 and now.depth > 0: self.clear_layer(now) self.kToLayer.pop(key_to_del) # 3.2 把新的加进去 self.data[key] = value self.add_key_to_layer(self.top_layer, key) self.kToLayer[key] = self.top_layer # --------------下面都是链表操作的辅助方法,注意链表的穿插不要丢了节点--------------- def add_key_to_layer(self, now: Layer, key: int): n = self.Node(key) now.queue[key] = n now.end.add(n) now.end = n def delete_key_of_layer(self, layer: Layer, key: int): node = layer.queue[key] if node == layer.end: layer.end = node.prev node.delete_self() layer.queue.pop(key) # 一级链表的更新(layer更新) def update_layer(self, now: Layer, key: int) -> Layer: self.delete_key_of_layer(now, key) # 注意那个depth+1 就是对应着频次的+1。ps 如果增加的值不定,就不应该用链表了,可能需要红黑树了 # 1. 当前已是链表最后 即频次最高 ->需要创建新的layer if now.next is None: next_layer = self.Layer(now.depth + 1) self.add_key_to_layer(next_layer, key) self.insert_layer(now, next_layer) out = next_layer # 2. 下一个layer直接跨了好几个深度 ->需要创建新的layer elif now.next.depth > now.depth + 1: next_layer = self.Layer(now.depth + 1) self.add_key_to_layer(next_layer, key) self.insert_layer(now, next_layer) out = next_layer # 3. 下一个layer刚好是目标深度 ->不需要创建新的layer else: self.add_key_to_layer(now.next, key) out = now.next # 如果这一层空了,就把它删掉,节省空间 if len(now.queue) == 0 and now.depth > 0: self.clear_layer(now) return out def insert_layer(self, now: Layer, target: Layer): if now.next is None: target.prev = now now.next = target self.end_layer = target else: target.prev = now target.next = now.next now.next.prev = target now.next = target def clear_layer(self, now: Layer): if now.next is None: now.prev.next = None self.end_layer = now.prev elif now.prev is None: now.next.prev = None else: now.prev.next = now.next now.next.prev = now.prev def pop_queue_from_layer(self, now: Layer) -> int: # 传进来时就是一级链表头了 # for top layer if len(now.queue) == 0 and now.depth == 0: now = now.next # 取二级链表的第一个key key = now.start.next.key self.delete_key_of_layer(now, key) if len(now.queue) == 0 and now.depth > 0: self.clear_layer(now) return key
- 作者:lalada
- 链接:https://leetcode-cn.com/problems/lfu-cache/solution/nei-cun-o1jiu-shuang-zhen-shi-jian-o1jiu-yong-hash/
- 来源:力扣(LeetCode)
- 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。