LFU Cache
HardPrompt
Design and implement a data structure for a Least Frequently Used (LFU) cache.
Implement the LFUCache class:
LFUCache(int capacity)Initializes the object with thecapacityof the data structure.int get(int key)Gets the value of thekeyif thekeyexists in the cache. Otherwise, returns-1.void put(int key, int value)Update the value of thekeyif present, or inserts thekeyif 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 usedkeywould 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
Constraints:
1 <= capacity <= 1040 <= key <= 1050 <= value <= 109- At most
2 * 105calls will be made togetandput.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach uses a single hash map for O(1) lookups. Each entry in the map stores not only the value but also metadata: its usage frequency and a timestamp for its last access. When an eviction is necessary, the entire cache is scanned to find the item with the lowest frequency. Any ties are broken by choosing the item that was least recently used (i.e., has the oldest timestamp).
Algorithm
- Initialize a
HashMap<Integer, CacheEntry>to store cache data, whereCacheEntryholds the value, frequency, and alastAccessTime. - Maintain a global
timervariable, incremented on each operation to provide a sequential timestamp. - For
get(key):- Retrieve
CacheEntryfrom the map. If it doesn't exist, return -1. - Increment the entry's frequency.
- Update its
lastAccessTimewith the currenttimervalue and increment thetimer. - Return the value.
- Retrieve
- For
put(key, value):- If the key exists, update its value, increment its frequency, and update its
lastAccessTime. - If the key does not exist:
- Check if the cache is full (
map.size() == capacity). - If full, iterate through all entries in the map to find the one with the minimum frequency. If there's a tie, choose the one with the minimum
lastAccessTime. - Remove this LFU/LRU entry from the map.
- Create a new
CacheEntrywith frequency 1 and the current timestamp, and insert it into the map.
- Check if the cache is full (
- If the key exists, update its value, increment its frequency, and update its
Walkthrough
The implementation revolves around a HashMap<Integer, CacheEntry> and a global timer. The CacheEntry class is a simple container for the value, frequency, and lastAccessTime.
get(key) Operation:
This is straightforward. We look up the key in the map. If it exists, we update its frequency and timestamp, then return the value. This is an O(1) operation.
put(key, value) Operation:
If the key already exists, we update its value and metadata, similar to a get operation. If the key is new, we first check if the cache is full. If it is, the eviction process begins. We must perform a linear scan through all the entries in the hash map's values. During this scan, we keep track of the entry with the lowest frequency found so far, using the last access time as a tie-breaker. Once the victim entry is identified, it's removed. Finally, the new entry is inserted with a frequency of 1. This eviction scan makes the put operation O(C), where C is the capacity.
class LFUCache { class CacheEntry { int value; int frequency; int accessTime; CacheEntry(int value, int frequency, int accessTime) { this.value = value; this.frequency = frequency; this.accessTime = accessTime; } } private Map<Integer, CacheEntry> cache; private int capacity; private int timer; public LFUCache(int capacity) { this.cache = new HashMap<>(); this.capacity = capacity; this.timer = 0; } public int get(int key) { if (!cache.containsKey(key)) { return -1; } CacheEntry entry = cache.get(key); entry.frequency++; entry.accessTime = timer++; return entry.value; } public void put(int key, int value) { if (capacity == 0) { return; } if (cache.containsKey(key)) { CacheEntry entry = cache.get(key); entry.value = value; entry.frequency++; entry.accessTime = timer++; } else { if (cache.size() >= capacity) { int keyToEvict = -1; int minFreq = Integer.MAX_VALUE; int minTime = Integer.MAX_VALUE; for (Map.Entry<Integer, CacheEntry> mapEntry : cache.entrySet()) { CacheEntry current = mapEntry.getValue(); if (current.frequency < minFreq) { minFreq = current.frequency; minTime = current.accessTime; keyToEvict = mapEntry.getKey(); } else if (current.frequency == minFreq && current.accessTime < minTime) { minTime = current.accessTime; keyToEvict = mapEntry.getKey(); } } cache.remove(keyToEvict); } cache.put(key, new CacheEntry(value, 1, timer++)); } }}Complexity
Time
`get`: O(1) `put`: O(C), where C is the cache capacity. The need to scan the entire cache for eviction makes this approach inefficient for large capacities.
Space
O(C), where C is the cache capacity, to store the key-value pairs and their metadata in the hash map.
Trade-offs
Pros
Relatively simple to conceptualize and implement.
The
getoperation is efficient with O(1) time complexity.
Cons
The
putoperation has a time complexity of O(C) where C is the capacity, which fails to meet the problem's O(1) requirement.Performance degrades significantly as the cache capacity increases.
This approach improves upon the linear scan by using a min-heap (implemented as a PriorityQueue in Java) to more efficiently find the LFU/LRU item for eviction. The heap is ordered by frequency, and then by access time as a tie-breaker. This reduces the time to find the eviction candidate from O(C) to O(log C). A key challenge is updating an item's priority in the heap, which is handled using a "lazy removal" strategy: instead of updating an item, we add a new version and mark the old one as stale, to be cleaned up during eviction.
Algorithm
- Use a
HashMap<Integer, CacheEntry>for O(1) lookups.CacheEntrystores key, value, frequency, and access time. - Use a
PriorityQueue<CacheEntry>(min-heap) ordered first by frequency, then by access time. - Maintain a global
timer. - For
get(key)orput(key, value)on an existing key:- Retrieve the old
CacheEntryfrom the map. - Create a new
CacheEntrywith the updated frequency and timestamp. - Update the map to point to the new entry.
- Add the new entry to the heap. The old entry in the heap is now considered "stale".
- Retrieve the old
- For
put(key, value)on a new key:- If the cache is full, perform eviction.
- Eviction: Repeatedly poll from the heap. If the polled entry is stale (i.e., its data doesn't match the entry in the map for that key), discard it and poll again. Continue until a valid entry is polled.
- Remove the valid evicted key from the map.
- Create a new
CacheEntryand add it to both the map and the heap.
Walkthrough
This method combines a hash map for fast lookups with a min-heap for efficient eviction selection.
- Data Structures: A
HashMap<Integer, CacheEntry>provides O(1) access to entries. APriorityQueue<CacheEntry>keeps the entries sorted by(frequency, accessTime), so the LFU/LRU element is always at the top. - Lazy Removal: A standard heap doesn't support efficient updates of arbitrary elements. To work around this, when an entry's frequency is updated (via
getorput), we don't remove the old version from the heap. Instead, we add the new, updated version to the heap and update the hash map to point to this new version. The old entry in the heap becomes "stale". - Eviction: When we need to evict, we poll the top element from the heap. We must verify if it's stale by comparing it with the entry stored in our hash map. If
map.get(polledEntry.key)is not the same aspolledEntry, it's stale, and we discard it and poll again. This continues until we find a valid, non-stale entry to evict.
class LFUCache { class CacheEntry implements Comparable<CacheEntry> { int key, value, frequency, accessTime; CacheEntry(int key, int value, int frequency, int accessTime) { this.key = key; this.value = value; this.frequency = frequency; this.accessTime = accessTime; } @Override public int compareTo(CacheEntry other) { if (this.frequency != other.frequency) { return this.frequency - other.frequency; } return this.accessTime - other.accessTime; } } private Map<Integer, CacheEntry> cache; private PriorityQueue<CacheEntry> minHeap; private int capacity; private int timer; public LFUCache(int capacity) { this.cache = new HashMap<>(); this.minHeap = new PriorityQueue<>(); this.capacity = capacity; this.timer = 0; } public int get(int key) { if (!cache.containsKey(key)) { return -1; } CacheEntry entry = cache.get(key); CacheEntry newEntry = new CacheEntry(key, entry.value, entry.frequency + 1, timer++); cache.put(key, newEntry); minHeap.offer(newEntry); return entry.value; } public void put(int key, int value) { if (capacity == 0) { return; } if (cache.containsKey(key)) { CacheEntry entry = cache.get(key); CacheEntry newEntry = new CacheEntry(key, value, entry.frequency + 1, timer++); cache.put(key, newEntry); minHeap.offer(newEntry); } else { if (cache.size() >= capacity) { while (!minHeap.isEmpty()) { CacheEntry toEvict = minHeap.poll(); // Check if it's a stale entry if (cache.containsKey(toEvict.key) && cache.get(toEvict.key) == toEvict) { cache.remove(toEvict.key); break; } } } CacheEntry newEntry = new CacheEntry(key, value, 1, timer++); cache.put(key, newEntry); minHeap.offer(newEntry); } }}Complexity
Time
`get`: O(log C) `put`: O(log C) Both operations involve adding to the heap. The eviction process in `put` might poll multiple stale entries, but this is amortized to O(log C).
Space
O(C + M), where C is the capacity and M is the number of `get`/`put` operations that cause updates. The heap can store stale entries, so its size can exceed the capacity.
Trade-offs
Pros
Much more efficient than the linear scan approach, with logarithmic time complexity.
Conceptually builds upon the brute-force idea by optimizing the search for the eviction candidate.
Cons
Does not meet the strict O(1) time complexity requirement.
Space complexity can be higher than the capacity due to stale entries accumulating in the heap.
The logic for handling stale entries adds complexity to the implementation.
This is the optimal O(1) solution. It achieves constant time complexity for both get and put by using a sophisticated combination of data structures. A primary hash map provides O(1) lookups. A second hash map groups cache items by their frequency. Each frequency group is a doubly linked list (DLL) that maintains the least-recently-used (LRU) order for that specific frequency. This allows for O(1) eviction of the LFU item, and O(1) tie-breaking using the LRU policy.
Algorithm
- Use a
cachemap (HashMap<Integer, Node>) for O(1) key-to-node lookups. - Use a
freqMap(HashMap<Integer, DoublyLinkedList>) to group nodes by frequency. Each frequency maps to a DLL of nodes with that frequency. - The DLLs maintain LRU order: new/updated nodes are added to the head, and eviction candidates are removed from the tail.
- Maintain a
minFrequencyinteger to track the lowest frequency in the cache, allowing for O(1) identification of the LFU list. updateNode(node): A helper function to promote a node to a higher frequency. It removes the node from its current frequency's DLL and adds it to the head of the next frequency's DLL. It also updatesminFrequencyif a frequency list becomes empty.get(key): Find the node incache, callupdateNodeon it, and return its value.put(key, value):- If key exists, update its value and call
updateNode. - If new:
- If at capacity, evict: get the DLL for
minFrequency, remove its tail node, and remove that node's key from thecachemap. - Create a new node, add it to
cache, add it to the DLL for frequency 1, and resetminFrequencyto 1.
- If at capacity, evict: get the DLL for
- If key exists, update its value and call
Walkthrough
This approach masterfully combines hash maps and doubly linked lists to meet the O(1) time complexity requirement.
cache(HashMap<Integer, Node>): Maps a key to itsNode. TheNodecontains the key, value, frequency, and pointers (prev,next) for the DLL.freqMap(HashMap<Integer, DoublyLinkedList>): Maps a frequency count to aDoublyLinkedListinstance. This DLL contains all nodes with that frequency.DoublyLinkedList: A custom class that manages a list of nodes. It has methods to add a node to the head (addNode) and remove a node from any position (removeNode), both in O(1). It also tracks its head and tail.minFrequency: An integer that always points to the lowest frequency currently in the cache. This is the key to O(1) eviction, as we can directly access the correct DLL infreqMapusingfreqMap.get(minFrequency).
Operation Flow:
When a node is accessed (get or put), its frequency must be incremented. This means it must move from the DLL of its current frequency (f) to the DLL of the next frequency (f+1).
- It's removed from the
fDLL. - If the
fDLL becomes empty andfwas theminFrequency, we incrementminFrequency. - The node's frequency is updated, and it's added to the head of the
f+1DLL (which is created if it doesn't exist).
When a new node is inserted, it always has a frequency of 1. It's added to the head of the DLL for frequency 1, and minFrequency is reset to 1.
class LFUCache { class Node { int key, value, freq; Node prev, next; Node(int key, int value) { this.key = key; this.value = value; this.freq = 1; } } class DoublyLinkedList { Node head, tail; int size; DoublyLinkedList() { head = new Node(0, 0); // Dummy head tail = new Node(0, 0); // Dummy tail head.next = tail; tail.prev = head; size = 0; } void addNode(Node node) { // Add to head node.next = head.next; head.next.prev = node; head.next = node; node.prev = head; size++; } void removeNode(Node node) { node.prev.next = node.next; node.next.prev = node.prev; size--; } Node removeTail() { if (size == 0) return null; Node nodeToRemove = tail.prev; removeNode(nodeToRemove); return nodeToRemove; } } private final int capacity; private int minFrequency; private Map<Integer, Node> cache; private Map<Integer, DoublyLinkedList> freqMap; public LFUCache(int capacity) { this.capacity = capacity; this.minFrequency = 0; this.cache = new HashMap<>(); this.freqMap = new HashMap<>(); } private void updateNode(Node node) { DoublyLinkedList oldList = freqMap.get(node.freq); oldList.removeNode(node); if (node.freq == minFrequency && oldList.size == 0) { minFrequency++; } node.freq++; DoublyLinkedList newList = freqMap.getOrDefault(node.freq, new DoublyLinkedList()); newList.addNode(node); freqMap.put(node.freq, newList); } public int get(int key) { if (!cache.containsKey(key)) { return -1; } Node node = cache.get(key); updateNode(node); return node.value; } public void put(int key, int value) { if (capacity == 0) return; if (cache.containsKey(key)) { Node node = cache.get(key); node.value = value; updateNode(node); } else { if (cache.size() >= capacity) { DoublyLinkedList lfuList = freqMap.get(minFrequency); Node nodeToEvict = lfuList.removeTail(); cache.remove(nodeToEvict.key); } Node newNode = new Node(key, value); cache.put(key, newNode); minFrequency = 1; DoublyLinkedList newList = freqMap.getOrDefault(1, new DoublyLinkedList()); newList.addNode(newNode); freqMap.put(1, newList); } }}Complexity
Time
`get`: O(1) `put`: O(1) All constituent operations (hash map lookups/insertions, DLL additions/removals) are constant time.
Space
O(C), where C is the cache capacity. The `cache` map, `freqMap`, and all nodes within the linked lists will store a total of C items.
Trade-offs
Pros
Achieves the optimal O(1) average time complexity for both
getandputoperations.Space complexity is strictly bounded by the cache capacity O(C).
Cons
Significantly more complex to implement correctly compared to other approaches.
Requires careful management of pointers in the doubly linked list and state across multiple data structures.
Solutions
Solution
public class LFU_Cache { public class LFUCache { // Save the key, value HashMap < Integer , Integer > vals ; // Save the key to the value of the number of visits HashMap < Integer , Integer > counts ; // 频率和一个里面所有key都是当前频率的list之间的映射 HashMap < Integer , LinkedHashSet < Integer >> lists ; int capacity ; // Initialize the frequency of data occurrences int min = - 1 ; public LFUCache ( int cap ) { capacity = cap ; vals = new HashMap <>(); counts = new HashMap <>(); lists = new HashMap <>(); } public int get ( int key ) { if (! vals . containsKey ( key )) return - 1 ; int count = counts . get ( key ); counts . put ( key , count + 1 ); lists . get ( count ). remove ( key ); // Determine whether min should add 1 if ( count == min && lists . get ( count ). size () == 0 ) { min ++; } if (! lists . containsKey ( count + 1 )) { lists . put ( count + 1 , new LinkedHashSet <>()); } lists . get ( count + 1 ). add ( key ); return vals . get ( key ); } public void put ( int key , int value ) { if ( capacity <= 0 ) return ; if ( vals . containsKey ( key )) { vals . put ( key , value ); get ( key ); return ; } if ( vals . size () >= capacity ) { int minFreKey = lists . get ( min ). iterator (). next (); lists . get ( min ). remove ( minFreKey ); vals . remove ( minFreKey ); counts . remove ( minFreKey ); } vals . put ( key , value ); counts . put ( key , 1 ); min = 1 ; if (! lists . containsKey ( 1 )) { lists . put ( 1 , new LinkedHashSet <>()); } lists . get ( 1 ). add ( key ); } } } ############ class LFUCache { private final Map < Integer , Node > map ; private final Map < Integer , DoublyLinkedList > freqMap ; private final int capacity ; private int minFreq ; public LFUCache ( int capacity ) { this . capacity = capacity ; map = new HashMap <>( capacity , 1 ); freqMap = new HashMap <>(); } public int get ( int key ) { if ( capacity == 0 ) { return - 1 ; } if (! map . containsKey ( key )) { return - 1 ; } Node node = map . get ( key ); incrFreq ( node ); return node . value ; } public void put ( int key , int value ) { if ( capacity == 0 ) { return ; } if ( map . containsKey ( key )) { Node node = map . get ( key ); node . value = value ; incrFreq ( node ); return ; } if ( map . size () == capacity ) { DoublyLinkedList list = freqMap . get ( minFreq ); map . remove ( list . removeLast (). key ); } Node node = new Node ( key , value ); addNode ( node ); map . put ( key , node ); minFreq = 1 ; } private void incrFreq ( Node node ) { int freq = node . freq ; DoublyLinkedList list = freqMap . get ( freq ); list . remove ( node ); if ( list . isEmpty ()) { freqMap . remove ( freq ); if ( freq == minFreq ) { minFreq ++; } } node . freq ++; addNode ( node ); } private void addNode ( Node node ) { int freq = node . freq ; DoublyLinkedList list = freqMap . getOrDefault ( freq , new DoublyLinkedList ()); list . addFirst ( node ); freqMap . put ( freq , list ); } private static class Node { int key ; int value ; int freq ; Node prev ; Node next ; Node ( int key , int value ) { this . key = key ; this . value = value ; this . freq = 1 ; } } private static class DoublyLinkedList { private final Node head ; private final Node tail ; public DoublyLinkedList () { head = new Node (- 1 , - 1 ); tail = new Node (- 1 , - 1 ); head . next = tail ; tail . prev = head ; } public void addFirst ( Node node ) { node . prev = head ; node . next = head . next ; head . next . prev = node ; head . next = node ; } public Node remove ( Node node ) { node . next . prev = node . prev ; node . prev . next = node . next ; node . next = null ; node . prev = null ; return node ; } public Node removeLast () { return remove ( tail . prev ); } public boolean isEmpty () { return head . next == tail ; } } }from collections import defaultdict class LFUCache : def __init__ ( self , capacity : int ): self . capacity = capacity self . key_to_value = {} self . key_to_freq = defaultdict ( int ) self . freq_to_keys = defaultdict ( OrderedDict ) self . min_freq = 0 def get ( self , key : int ) -> int : if key not in self . key_to_value : return - 1 # Update the frequency self . _update_frequency ( key ) return self . key_to_value [ key ] def put ( self , key : int , value : int ) -> None : if self . capacity == 0 : return if key in self . key_to_value : # Update the value and frequency self . key_to_value [ key ] = value self . _update_frequency ( key ) else : if len ( self . key_to_value ) >= self . capacity : # Evict the least frequently used key self . _evict () # Add the new key-value pair self . key_to_value [ key ] = value self . key_to_freq [ key ] = 1 self . freq_to_keys [ 1 ][ key ] = None self . min_freq = 1 def _update_frequency ( self , key : int ) -> None : freq = self . key_to_freq [ key ] del self . freq_to_keys [ freq ][ key ] if not self . freq_to_keys [ freq ]: # If there are no keys with the previous frequency, update min_freq if self . min_freq == freq : self . min_freq += 1 del self . freq_to_keys [ freq ] freq += 1 self . key_to_freq [ key ] = freq self . freq_to_keys [ freq ][ key ] = None def _evict ( self ) -> None : keys = self . freq_to_keys [ self . min_freq ] evict_key , _ = keys . popitem ( last = False ) del self . key_to_value [ evict_key ] del self . key_to_freq [ evict_key ] # Your LFUCache object will be instantiated and called as such: # obj = LFUCache(capacity) # param_1 = obj.get(key) # obj.put(key,value) ########## class Node : def __init__ ( self , key : int , value : int ) -> None : self . key = key self . value = value self . freq = 1 self . prev = None self . next = None class DoublyLinkedList : def __init__ ( self ) -> None : self . head = Node ( - 1 , - 1 ) self . tail = Node ( - 1 , - 1 ) self . head . next = self . tail self . tail . prev = self . head def add_first ( self , node : Node ) -> None : node . prev = self . head node . next = self . head . next self . head . next . prev = node self . head . next = node def remove ( self , node : Node ) -> Node : node . next . prev = node . prev node . prev . next = node . next node . next , node . prev = None , None return node def remove_last ( self ) -> Node : return self . remove ( self . tail . prev ) def is_empty ( self ) -> bool : return self . head . next == self . tail class LFUCache : def __init__ ( self , capacity : int ): self . capacity = capacity self . min_freq = 0 self . map = defaultdict ( Node ) self . freq_map = defaultdict ( DoublyLinkedList ) def get ( self , key : int ) -> int : if self . capacity == 0 or key not in self . map : return - 1 node = self . map [ key ] self . incr_freq ( node ) return node . value def put ( self , key : int , value : int ) -> None : if self . capacity == 0 : return if key in self . map : node = self . map [ key ] node . value = value self . incr_freq ( node ) return if len ( self . map ) == self . capacity : ls = self . freq_map [ self . min_freq ] node = ls . remove_last () self . map . pop ( node . key ) node = Node ( key , value ) self . add_node ( node ) self . map [ key ] = node self . min_freq = 1 def incr_freq ( self , node : Node ) -> None : freq = node . freq ls = self . freq_map [ freq ] ls . remove ( node ) if ls . is_empty (): self . freq_map . pop ( freq ) if freq == self . min_freq : self . min_freq += 1 node . freq += 1 self . add_node ( node ) def add_node ( self , node : Node ) -> None : freq = node . freq ls = self . freq_map [ freq ] ls . add_first ( node ) self . freq_map [ freq ] = ls # Your LFUCache object will be instantiated and called as such: # obj = LFUCache(capacity) # param_1 = obj.get(key) # obj.put(key,value)class Node { public: int key ; int value ; int freq ; Node * prev ; Node * next ; Node ( int key , int value ) { this -> key = key ; this -> value = value ; this -> freq = 1 ; this -> prev = nullptr ; this -> next = nullptr ; } }; class DoublyLinkedList { public: Node * head ; Node * tail ; DoublyLinkedList () { this -> head = new Node ( - 1 , - 1 ); this -> tail = new Node ( - 1 , - 1 ); this -> head -> next = this -> tail ; this -> tail -> prev = this -> head ; } void addFirst ( Node * node ) { node -> prev = this -> head ; node -> next = this -> head -> next ; this -> head -> next -> prev = node ; this -> head -> next = node ; } Node * remove ( Node * node ) { node -> next -> prev = node -> prev ; node -> prev -> next = node -> next ; node -> next = nullptr ; node -> prev = nullptr ; return node ; } Node * removeLast () { return remove ( this -> tail -> prev ); } bool isEmpty () { return this -> head -> next == this -> tail ; } }; class LFUCache { public: LFUCache ( int capacity ) { this -> capacity = capacity ; this -> minFreq = 0 ; } int get ( int key ) { if ( capacity == 0 || map . find ( key ) == map . end ()) { return - 1 ; } Node * node = map [ key ]; incrFreq ( node ); return node -> value ; } void put ( int key , int value ) { if ( capacity == 0 ) { return ; } if ( map . find ( key ) != map . end ()) { Node * node = map [ key ]; node -> value = value ; incrFreq ( node ); return ; } if ( map . size () == capacity ) { DoublyLinkedList * list = freqMap [ minFreq ]; Node * node = list -> removeLast (); map . erase ( node -> key ); } Node * node = new Node ( key , value ); addNode ( node ); map [ key ] = node ; minFreq = 1 ; } private: int capacity ; int minFreq ; unordered_map < int , Node *> map ; unordered_map < int , DoublyLinkedList *> freqMap ; void incrFreq ( Node * node ) { int freq = node -> freq ; DoublyLinkedList * list = freqMap [ freq ]; list -> remove ( node ); if ( list -> isEmpty ()) { freqMap . erase ( freq ); if ( freq == minFreq ) { minFreq ++ ; } } node -> freq ++ ; addNode ( node ); } void addNode ( Node * node ) { int freq = node -> freq ; if ( freqMap . find ( freq ) == freqMap . end ()) { freqMap [ freq ] = new DoublyLinkedList (); } DoublyLinkedList * list = freqMap [ freq ]; list -> addFirst ( node ); freqMap [ freq ] = list ; } }; /** * Your LFUCache object will be instantiated and called as such: * LFUCache* obj = new LFUCache(capacity); * int param_1 = obj->get(key); * obj->put(key,value); */Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.