Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get
and put
.
get(key)
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.put(key, value)
- Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.
Note that the number of times an item is used is the number of calls to the get
and put
functions for that item since it was inserted. This number is set to zero when the item is removed.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LFUCache cache = new LFUCache( 2 /* capacity */ ); cache.put(1, 1); cache.put(2, 2); cache.get(1); // returns 1 cache.put(3, 3); // evicts key 2 cache.get(2); // returns -1 (not found) cache.get(3); // returns 3. cache.put(4, 4); // evicts key 1. cache.get(1); // returns -1 (not found) cache.get(3); // returns 3 cache.get(4); // returns 4
------------------------------------------------------------------
这题需要知道频率,自然容易想到用一个dict来记录频率的key,dict中key对应的一个双向链表,用以前LRU的方式来维护这个双向链表,道理比较简单,题目的难点是操作比较多,容易乱和出错。个人总结了以下套路,从顶层入手,把链表节点搬家这件事情抽象成一个函数,把顶层的操作隐射成底层的函数,然后再去完成底层的代码。这种套路对于LRU也适用。
class Cache:
def __init__(self, capacity: int):
self._size,self._cap = 0,capacity
#需要多少零件
def renew(self,node):
self.try_delete_in_llst(node) #先试图删
#然后挪到node到该去的地方
def get(self, key: int) -> int:
if (key in self._dic):
node = self._dic[key]
self.renew(node) #用来链表节点挪动
return node._v
return -1
def put(self, key: int, value: int) -> None:
if (self._cap > 0):
node = get_Node() #如果有就从现有的拿,如果没有就新new一个
if (self._size > self._cap):
self._size -= 1
deleted_key = self.try_delete_in_llst(该删除的节点)
self._dic.pop(deleted_key)
self.renew(node)
self._dic[key] = node
对于LFU,还需要注意几点:
- Dict的key也放在链表节点里,否则从node里没法对dict的key进行操作
- 何时更新最小频率?这部分适合放在renew的函数里,同时注意有两个条件:最小频率节点搬走 或者 新来一个频率为1的节点,最小频率节点搬走严格还需要清理一下dict,但是可以偷懒不做
- 用default_dict的参数是lambda表达式,可以帮助省一些代码
以下是LFU的代码:
from collections import defaultdict
class Node:
def __init__(self,k,v):
self._k,self._v,self._freq = k,v,0
self._pre,self._nxt = None, None
class LFUCache:
def get_empty_llst(self):
head,tail = Node(0,0),Node(0,0)
head._nxt, tail._pre = tail, head
return (head,tail)
def __init__(self, capacity: int):
self._size,self._cap,self._min_freq = 0,capacity,0
self._key_dic, self._freq_dic = {}, defaultdict(self.get_empty_llst)
def get(self, key: int) -> int:
if key in self._key_dic:
node = self._key_dic[key]
self.move_update_freq(node)
return node._v
return -1
def try_delete_in_llst(self, node):
if (node._pre):
pre,nxt = node._pre, node._nxt
node._nxt._pre,node._pre._nxt = pre,nxt
return node._k
def move_update_freq(self, node):
node._freq += 1
self.try_delete_in_llst(node)
head,tail = self._freq_dic[node._freq]
headnxt = head._nxt
head._nxt,headnxt._pre,node._pre,node._nxt = node,node,head,headnxt #insert after head
#bug3: miss node._freq == 1
if (node._freq == 1 or (self._min_freq == node._freq - 1 and self._freq_dic[self._min_freq][0]._nxt == self._freq_dic[self._min_freq][1])):
self._min_freq = node._freq
def put(self, key: int, value: int) -> None:
if self._cap > 0:
if key in self._key_dic:
node = self._key_dic[key]
node._v = value
else:
node = Node(key,value)
self._size += 1
if (self._size > self._cap):
self._size -= 1 #bug2: forget this line
deleted_key = self.try_delete_in_llst(self._freq_dic[self._min_freq][1]._pre)
self._key_dic.pop(deleted_key)
self._key_dic[key] = node #bug1: forget this line
self.move_update_freq(node)
# cache = LFUCache(2)
# cache.put(1, 1)
# cache.put(2, 2)
# print(cache.get(1)) # returns 1
# cache.put(3, 3) # evicts key 2
# print(cache.get(2)) # returns -1 (not found)
# print(cache.get(3)) # returns 3.
# cache.put(4, 4) # evicts key 1.
# print(cache.get(1)) # returns -1 (not found)
# print(cache.get(3)) # returns 3
# print(cache.get(4)) # returns 4
cache = LFUCache(3)
cache.put(2, 2)
cache.put(1, 1)
print(cache.get(2)) #2
print(cache.get(1)) #1
print(cache.get(2)) #2
cache.put(3, 3)
cache.put(4, 4)
print(cache.get(3)) #-1
print(cache.get(2)) #2
print(cache.get(1)) #1
print(cache.get(4)) #4
再补一个用这种思路写的LRU代码:
class Node:
def __init__(self,k,v):
self._k,self._v = k,v
self._pre,self._nxt = None,None
class LRUCache:
def __init__(self, capacity: int):
self._size,self._cap = 0,capacity
self._dic = {}
self._llst = self.get_empty_llst()
def get_empty_llst(self):
head,tail = Node(0,0),Node(0,0)
head._nxt, tail._pre = tail, head
return (head,tail)
def get(self, key: int) -> int:
if (key in self._dic):
node = self._dic[key]
self.renew(node)
return node._v
return -1
def try_delete_in_llst(self, node):
if (node._pre):
pre,nxt = node._pre, node._nxt
node._nxt._pre,node._pre._nxt = pre,nxt
return node._k
def renew(self, node):
self.try_delete_in_llst(node)
head,tail = self._llst
headnxt = head._nxt
head._nxt,headnxt._pre,node._pre,node._nxt = node,node,head,headnxt #insert after head
def put(self, key: int, value: int) -> None:
if (self._cap > 0):
if (key in self._dic):
node = self._dic[key]
node._v = value
else:
node = Node(key,value)
self._size += 1
if (self._size > self._cap):
self._size -= 1
deleted_key = self.try_delete_in_llst(self._llst[1]._pre)
self._dic.pop(deleted_key)
self.renew(node)
self._dic[key] = node