esign and implement a data structure for a Least Frequently Used (LFU) cache.
Implement the LFUCache
class:
LFUCache(int capacity)
Initializes the object with thecapacity
of the data structure.int get(int key)
Gets the value of thekey
if thekey
exists in the cache. Otherwise, returns-1
.void put(int key, int value)
Update the value of thekey
if present, or inserts thekey
if not already present. When the cache reaches itscapacity
, it should invalidate and remove the least frequently used key before inserting a new item. For this problem, when there is a tie (i.e., two or more keys with the same frequency), the least recently usedkey
would be invalidated.
To determine the least frequently used key, a use counter is maintained for each key in the cache. The key with the smallest use counter is the least frequently used key.
When a key is first inserted into the cache, its use counter is set to 1
(due to the put
operation). The use counter for a key in the cache is incremented either a get
or put
operation is called on it.
The functions get
and put
must each run in O(1)
average time complexity.
Example 1:
Input ["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]] Output [null, null, null, 1, null, -1, 3, null, -1, 3, 4] Explanation // cnt(x) = the use counter for key x // cache=[] will show the last used order for tiebreakers (leftmost element is most recent) LFUCache lfu = new LFUCache(2); lfu.put(1, 1); // cache=[1,_], cnt(1)=1 lfu.put(2, 2); // cache=[2,1], cnt(2)=1, cnt(1)=1 lfu.get(1); // return 1 // cache=[1,2], cnt(2)=1, cnt(1)=2 lfu.put(3, 3); // 2 is the LFU key because cnt(2)=1 is the smallest, invalidate 2. // cache=[3,1], cnt(3)=1, cnt(1)=2 lfu.get(2); // return -1 (not found) lfu.get(3); // return 3 // cache=[3,1], cnt(3)=2, cnt(1)=2 lfu.put(4, 4); // Both 1 and 3 have the same cnt, but 1 is LRU, invalidate 1. // cache=[4,3], cnt(4)=1, cnt(3)=2 lfu.get(1); // return -1 (not found) lfu.get(3); // return 3 // cache=[3,4], cnt(4)=1, cnt(3)=3 lfu.get(4); // return 4 // cache=[3,4], cnt(4)=2, cnt(3)=3
Constraints:
0 <= capacity <= 104
0 <= key <= 105
0 <= value <= 109
- At most
2 * 105
calls will be made toget
andput
.
题目要设计一个 Least Frequently Used (LFU) Cache,即最不经常使用缓存,跟146. LRU Cache 一样是缓存技术的一种算法。LFU与LRU的不同在于当缓存满了时对要丢弃的数据的处理机制不一样,LFU是把最不经常使用也就是被访问的频率最小的那个单元丢弃,要是有多个单元的被访问频率是一样的,那就遵循LRU规则。
LFU的实现还是基于双向链表,由于是根据频率大小来选择要丢弃的数据单元并且同一频率下有可能有多个数据单元,因此要针对每个频率值维护一个链表。链表内的数据丢弃原则是基于LRU的,只要始终把最新的数据插入链表头,那么链表尾的数据就是least recently used的。
LFU接口函数:
LFUCache(int capacity)
初始化数据接口和相关变量。- self.size = capacity:#缓存大小
self.f2list = {}:#用于存储频率与其对应链表的映射关系
self.k2node = {} #用于存储key与数据单元(节点)的映射关以便快速查找到节点。
self.cnt = 0 # 用于记录当前缓存里数据单元的个数
self.minFreq = 1 #用于追踪当前的最小频率,从1开始
self.f2list[1] = self.create() #为频率1创建一个只有伪头节点的空链表 int get(int key)
给定一个key,如果存在则返回对应的value,如不存在返回-1。另外如果key存在说明其对应的数据单元又被访问了一次,节点访问频率值freq加1,这时要把该节点从freq对应的链表中删除,插入到freq+1对应的链表中(如不存在则要为freq+1先创造一个新链表)。还有如果被访问的节点的原频率就是最小频率,还需要判断是否要更新当前最小频率self.minFreq,要是freq对应的链表被删掉一个节点后变为空则self.minFreq加1,否则不变。void put(int key, int value)
给定一对key-value,如key已经存在则更新key对应的value,并且其对应的数据单元又被访问了一次,需要做跟get()一样的处理。如key不存在则需插入新的key-value即在频率为1的链表中插入一个新节点,在插入一个新节点前需要判断缓存是否已满,如果慢则需要丢弃一个数据单元,最小频率值self.minFreq对应的链表尾部的那个节点就是要删除的节点,将其删除。然后创建一个新节点插入到频率为1的链表中,self.k2node增加一对key-node,节点个数self.cnt加1,最小频率self.minFreq复位成1。
class ListNode:
def __init__(self, key = None, val = None):
self.key = key
self.val = val
self.freq = 1
self.prev = None
self.next = None
class LFUCache:
def __init__(self, capacity: int):
self.size = capacity
self.f2list = {}
self.k2node = {}
self.cnt = 0
self.minFreq = 1
self.f2list[1] = self.create()
def get(self, key: int) -> int:
if self.size == 0 or key not in self.k2node :
return -1
node = self.k2node[key]
self.delete(node)
head = self.f2list[node.freq]
if self.minFreq == node.freq and self.isEmpty(head):
self.minFreq += 1
node.freq += 1
if node.freq not in self.f2list:
self.f2list[node.freq] = self.create()
head = self.f2list[node.freq]
self.insert(head, node)
return node.val
def put(self, key: int, value: int) -> None:
if self.size == 0:
return
if key in self.k2node:
node = self.k2node[key]
node.val = value
self.get(key)
return
if self.cnt == self.size:
head = self.f2list[self.minFreq]
self.k2node.pop(head.prev.key)
self.delete(head.prev)
self.cnt -= 1
node = ListNode(key, value)
self.minFreq = 1
head = self.f2list[1]
self.insert(head, node)
self.k2node[key] = node
self.cnt += 1
def create(self):
head = ListNode()
head.next = head
head.prev = head
return head
def insert(self, head, node):
node.next = head.next
head.next.prev = node
head.next = node
node.prev = head
def delete(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def isEmpty(self, head):
return head.next == head