LFU Cache
Problem Statement
Design 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=[4,3], cnt(4)=2, cnt(3)=3
Constaints:
- 0 <= capacity <= 104
- 0 <= key <= 105
- 0 <= value <= 109
- At most 2 * 105 calls will be made to
get
andput
.
Code
Python
from collections import defaultdict
from collections import OrderedDict
class Node:
def __init__(self, key, val, count):
self.key = key
self.val = val
self.count = count
class LFUCache(object):
def __init__(self, capacity):
"""
:type capacity: int
"""
self.cap = capacity
self.key2node = {}
self.count2node = defaultdict(OrderedDict)
self.minCount = None
def get(self, key):
"""
:type key: int
:rtype: int
"""
if key not in self.key2node:
return -1
node = self.key2node[key]
del self.count2node[node.count][key]
# clean memory
if not self.count2node[node.count]:
del self.count2node[node.count]
node.count += 1
self.count2node[node.count][key] = node
# NOTICE check minCount!!!
if not self.count2node[self.minCount]:
self.minCount += 1
return node.val
def put(self, key, value):
"""
:type key: int
:type value: int
:rtype: void
"""
if not self.cap:
return
if key in self.key2node:
self.key2node[key].val = value
self.get(key) # NOTICE, put makes count+1 too
return
if len(self.key2node) == self.cap:
# popitem(last=False) is FIFO, like queue
# it return key and value!!!
k, n = self.count2node[self.minCount].popitem(last=False)
del self.key2node[k]
self.count2node[1][key] = self.key2node[key] = Node(key, value, 1)
self.minCount = 1
return