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 近期最少使用缓存