LFU Cache
Design and implement a data structure for a Least Frequently Used (LFU) cache. Implement the `LFUCache` class: - `LFUCache(int capacity)` Initializes the object with the `capacity` of the data structure. - `int get(int key)` Gets the value of the `key` if the `key` exists in the cache. Otherwise, returns `-1`. - `void put(int key, int value)` Update the value of the `key` if present, or inserts the `key` if not already present. When the cache reaches its `capacity`, 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 used** key 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.
Examples
Constraints
0 <= capacity <= 10^40 <= key <= 10^50 <= value <= 10^9At most 2 * 10^5 calls will be made to get and put.
Optimal Solution
Approach
To achieve O(1) time complexity for both `get` and `put` operations, we can combine a hash map with doubly linked lists. 1. **Data Structures**: * **Node**: Stores the `key`, `value`, and its `frequency`. It also has `prev` and `next` pointers to be used in a doubly linked list. * **Doubly Linked List (DLL)**: For a specific frequency, we maintain a DLL of nodes. The head represents the most recently used (MRU) node, and the tail represents the least recently used (LRU) node. * **Key Map (`key_to_node`)**: A hash map mapping a `key` to its corresponding `Node`. This allows O(1) access to any node. * **Frequency Map (`freq_to_dll`)**: A hash map mapping a `frequency` to a `Doubly Linked List`. This keeps track of all nodes having the same frequency, ordered by recency. * **min_freq**: An integer tracking the minimum frequency among all nodes currently in the cache. 2. **Get Operation**: * If the `key` is not in `key_to_node`, return -1. * Otherwise, fetch the node. We need to update its frequency: * Remove the node from the DLL in `freq_to_dll` corresponding to its current frequency. * If this DLL becomes empty and the node's frequency was equal to `min_freq`, increment `min_freq`. * Increment the node's frequency. * Add the node to the head of the DLL in `freq_to_dll` corresponding to its new frequency. * Return the node's value. 3. **Put Operation**: * If `capacity` is 0, do nothing. * If the `key` already exists, update its value and perform the same frequency update logic as in the `get` operation. * If the `key` is new: * If the cache is at full capacity, we need to evict the LFU/LRU node: * Get the DLL corresponding to `min_freq` from `freq_to_dll`. * Remove the LRU node (which is at the tail of this DLL). * Remove the corresponding key from `key_to_node`. * Create a new node with frequency 1. * Add it to `key_to_node`. * Add it to the head of the DLL for frequency 1 in `freq_to_dll`. * Reset `min_freq` to 1.