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.
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
双向链表(Doubly Linked List) + 哈希表(Hash Table)
Java:
public class LFUCache {
Node head = null;
final int capacity;
Map<Integer, Integer> valueMap;
Map<Integer, Node> nodeMap; public LFUCache (int capacity) {
this.capacity = capacity;
valueMap = new HashMap<>(this.capacity, 1f);
nodeMap = new HashMap<>(this.capacity, 1f);
} public int get(int key) {
if (valueMap.containsKey(key)) increase(key, valueMap.get(key));
return valueMap.getOrDefault(key, -1);
} private void increase(int key, int value) {
Node node = nodeMap.get(key);
node.keys.remove(key);
if (Objects.isNull(node.next)) node.next = new Node(node, null, 1 + node.count, key);
else if (node.next.count == node.count + 1) node.next.keys.add(key);
else node.next = node.next.prev = new Node(node, node.next, node.count + 1, key);
nodeMap.put(key, node.next);
valueMap.put(key, value);
if (node.keys.isEmpty()) remove(node);
} private void remove(Node node) {
if (head == node) head = node.next;
else node.prev.next = node.next;
if (Objects.nonNull(node.next)) node.next.prev = node.prev;
} public void set(int key, int value) {
if (0 == this.capacity) return;
if (valueMap.containsKey(key)) {
increase(key, value);
} else {
if (valueMap.size() == this.capacity) remove();
valueMap.put(key, value);
add(key);
}
} private void add(int key) {
if (Objects.isNull(head)) head = new Node(null, null, 1, key);
else if (head.count == 1) head.keys.add(key);
else head = head.prev = new Node(null, head, 1, key);
nodeMap.put(key, head);
} private void remove() {
if (Objects.isNull(head)) return;
int oldest = head.keys.iterator().next();
head.keys.remove(oldest);
if (head.keys.isEmpty()) remove(head);
nodeMap.remove(oldest);
valueMap.remove(oldest);
} class Node {
public Node prev, next;
public final int count;
public LinkedHashSet<Integer> keys = new LinkedHashSet<>(); public Node(Node prev, Node next, int count, int key) {
this.prev = prev;
this.next = next;
this.count = count;
keys.add(key);
}
}
}
Python:
class KeyNode(object):
def __init__(self, key, value, freq = 1):
self.key = key
self.value = value
self.freq = freq
self.prev = self.next = None class FreqNode(object):
def __init__(self, freq, prev, next):
self.freq = freq
self.prev = prev
self.next = next
self.first = self.last = None class LFUCache(object): def __init__(self, capacity):
""" :type capacity: int
"""
self.capacity = capacity
self.keyDict = dict()
self.freqDict = dict()
self.head = None def get(self, key):
"""
:type key: int
:rtype: int
"""
if key in self.keyDict:
keyNode = self.keyDict[key]
value = keyNode.value
self.increase(key, value)
return value
return -1 def set(self, key, value):
"""
:type key: int
:type value: int
:rtype: void
"""
if self.capacity == 0:
return
if key in self.keyDict:
self.increase(key, value)
return
if len(self.keyDict) == self.capacity:
self.removeKeyNode(self.head.last)
self.insertKeyNode(key, value) def increase(self, key, value):
"""
Increments the freq of an existing KeyNode<key, value> by 1.
:type key: str
:rtype: void
"""
keyNode = self.keyDict[key]
keyNode.value = value
freqNode = self.freqDict[keyNode.freq]
nextFreqNode = freqNode.next
keyNode.freq += 1
if nextFreqNode is None or nextFreqNode.freq > keyNode.freq:
nextFreqNode = self.insertFreqNodeAfter(keyNode.freq, freqNode)
self.unlinkKey(keyNode, freqNode)
self.linkKey(keyNode, nextFreqNode) def insertKeyNode(self, key, value):
"""
Inserts a new KeyNode<key, value> with freq 1.
:type key: str
:rtype: void
"""
keyNode = self.keyDict[key] = KeyNode(key, value)
freqNode = self.freqDict.get(1)
if freqNode is None:
freqNode = self.freqDict[1] = FreqNode(1, None, self.head)
if self.head:
self.head.prev = freqNode
self.head = freqNode
self.linkKey(keyNode, freqNode) def delFreqNode(self, freqNode):
"""
Delete freqNode.
:rtype: void
"""
prev, next = freqNode.prev, freqNode.next
if prev: prev.next = next
if next: next.prev = prev
if self.head == freqNode: self.head = next
del self.freqDict[freqNode.freq] def insertFreqNodeAfter(self, freq, node):
"""
Insert a new FreqNode(freq) after node.
:rtype: FreqNode
"""
newNode = FreqNode(freq, node, node.next)
self.freqDict[freq] = newNode
if node.next: node.next.prev = newNode
node.next = newNode
return newNode def removeKeyNode(self, keyNode):
"""
Remove keyNode
:rtype: void
"""
self.unlinkKey(keyNode, self.freqDict[keyNode.freq])
del self.keyDict[keyNode.key] def unlinkKey(self, keyNode, freqNode):
"""
Unlink keyNode from freqNode
:rtype: void
"""
next, prev = keyNode.next, keyNode.prev
if prev: prev.next = next
if next: next.prev = prev
if freqNode.first == keyNode: freqNode.first = next
if freqNode.last == keyNode: freqNode.last = prev
if freqNode.first is None: self.delFreqNode(freqNode) def linkKey(self, keyNode, freqNode):
"""
Link keyNode to freqNode
:rtype: void
"""
firstKeyNode = freqNode.first
keyNode.prev = None
keyNode.next = firstKeyNode
if firstKeyNode: firstKeyNode.prev = keyNode
freqNode.first = keyNode
if freqNode.last is None: freqNode.last = keyNode # Your LFUCache object will be instantiated and called as such:
# obj = LFUCache(capacity)
# param_1 = obj.get(key)
# obj.set(key,value)
Python:
class CacheNode(object):
def __init__(self, key, value, freq_node, pre, nxt):
self.key = key
self.value = value
self.freq_node = freq_node
self.pre = pre # previous CacheNode
self.nxt = nxt # next CacheNode def free_myself(self):
if self.freq_node.cache_head == self.freq_node.cache_tail:
self.freq_node.cache_head = self.freq_node.cache_tail = None
elif self.freq_node.cache_head == self:
self.nxt.pre = None
self.freq_node.cache_head = self.nxt
elif self.freq_node.cache_tail == self:
self.pre.nxt = None
self.freq_node.cache_tail = self.pre
else:
self.pre.nxt = self.nxt
self.nxt.pre = self.pre self.pre = None
self.nxt = None
self.freq_node = None class FreqNode(object):
def __init__(self, freq, pre, nxt):
self.freq = freq
self.pre = pre # previous FreqNode
self.nxt = nxt # next FreqNode
self.cache_head = None # CacheNode head under this FreqNode
self.cache_tail = None # CacheNode tail under this FreqNode def count_caches(self):
if self.cache_head is None and self.cache_tail is None:
return 0
elif self.cache_head == self.cache_tail:
return 1
else:
return '2+' def remove(self):
if self.pre is not None:
self.pre.nxt = self.nxt
if self.nxt is not None:
self.nxt.pre = self.pre pre = self.pre
nxt = self.nxt
self.pre = self.nxt = self.cache_head = self.cache_tail = None return (pre, nxt) def pop_head_cache(self):
if self.cache_head is None and self.cache_tail is None:
return None
elif self.cache_head == self.cache_tail:
cache_head = self.cache_head
self.cache_head = self.cache_tail = None
return cache_head
else:
cache_head = self.cache_head
self.cache_head.nxt.pre = None
self.cache_head = self.cache_head.nxt
return cache_head def append_cache_to_tail(self, cache_node):
cache_node.freq_node = self if self.cache_head is None and self.cache_tail is None:
self.cache_head = self.cache_tail = cache_node
else:
cache_node.pre = self.cache_tail
cache_node.nxt = None
self.cache_tail.nxt = cache_node
self.cache_tail = cache_node def insert_after_me(self, freq_node):
freq_node.pre = self
freq_node.nxt = self.nxt if self.nxt is not None:
self.nxt.pre = freq_node self.nxt = freq_node def insert_before_me(self, freq_node):
if self.pre is not None:
self.pre.nxt = freq_node freq_node.pre = self.pre
freq_node.nxt = self
self.pre = freq_node class LFUCache(object): def __init__(self, capacity):
self.cache = {} # {key: cache_node}
self.capacity = capacity
self.freq_link_head = None def get(self, key):
if key in self.cache:
cache_node = self.cache[key]
freq_node = cache_node.freq_node
value = cache_node.value self.move_forward(cache_node, freq_node) return value
else:
return -1 def set(self, key, value):
if self.capacity <= 0:
return -1 if key not in self.cache:
if len(self.cache) >= self.capacity:
self.dump_cache() self.create_cache(key, value)
else:
cache_node = self.cache[key]
freq_node = cache_node.freq_node
cache_node.value = value self.move_forward(cache_node, freq_node) def move_forward(self, cache_node, freq_node):
if freq_node.nxt is None or freq_node.nxt.freq != freq_node.freq + 1:
target_freq_node = FreqNode(freq_node.freq + 1, None, None)
target_empty = True
else:
target_freq_node = freq_node.nxt
target_empty = False cache_node.free_myself()
target_freq_node.append_cache_to_tail(cache_node) if target_empty:
freq_node.insert_after_me(target_freq_node) if freq_node.count_caches() == 0:
if self.freq_link_head == freq_node:
self.freq_link_head = target_freq_node freq_node.remove() def dump_cache(self):
head_freq_node = self.freq_link_head
self.cache.pop(head_freq_node.cache_head.key)
head_freq_node.pop_head_cache() if head_freq_node.count_caches() == 0:
self.freq_link_head = head_freq_node.nxt
head_freq_node.remove() def create_cache(self, key, value):
cache_node = CacheNode(key, value, None, None, None)
self.cache[key] = cache_node if self.freq_link_head is None or self.freq_link_head.freq != 0:
new_freq_node = FreqNode(0, None, None)
new_freq_node.append_cache_to_tail(cache_node) if self.freq_link_head is not None:
self.freq_link_head.insert_before_me(new_freq_node) self.freq_link_head = new_freq_node
else:
self.freq_link_head.append_cache_to_tail(cache_node)
C++:
class LFUCache {
public:
LFUCache(int capacity) {
cap = capacity;
} int get(int key) {
if (m.count(key) == 0) return -1;
freq[m[key].second].erase(iter[key]);
++m[key].second;
freq[m[key].second].push_back(key);
iter[key] = --freq[m[key].second].end();
if (freq[minFreq].size() == 0) ++minFreq;
return m[key].first;
} void put(int key, int value) {
if (cap <= 0) return;
if (get(key) != -1) {
m[key].first = value;
return;
}
if (m.size() >= cap) {
m.erase(freq[minFreq].front());
iter.erase(freq[minFreq].front());
freq[minFreq].pop_front();
}
m[key] = {value, 1};
freq[1].push_back(key);
iter[key] = --freq[1].end();
minFreq = 1;
} private:
int cap, minFreq;
unordered_map<int, pair<int, int>> m;
unordered_map<int, list<int>> freq;
unordered_map<int, list<int>::iterator> iter;
};
[LeetCode] 146. LRU Cache 近期最少使用缓存