LFU Cache
Hard
Subject: Design
Time Complexity
O(1)
Space Complexity
O(C)
Problem Description
Problem Statement
Design and implement a data structure for a Least Frequently Used (LFU) cache. Implement the LFUCache class with get(key) and put(key, value). When the cache reaches its capacity, it should invalidate and evict the least frequently used key. If there is a tie, evict the least recently used key. O(1) time required.
Example 1:
- Input:
["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]\n[[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]
Optimal Solution
PythonApproach: Hash Maps for Frequency and Nodes
Use a hash map to store keys mapped to nodes. Use another hash map mapping frequencies to a Doubly Linked List of nodes with that frequency (ordered by recency). Track the min_freq. On get/put, update the node's frequency, moving it to the next frequency's list.
class Node:
def __init__(self, key, val):
self.key = key
self.val = val
self.freq = 1
self.prev = self.next = None
class DLinkedList:
def __init__(self):
self.head = Node(None, None)
self.tail = Node(None, None)
self.head.next = self.tail
self.tail.prev = self.head
self.size = 0
def __len__(self): return self.size
def append(self, node):
node.next = self.head.next
node.prev = self.head
self.head.next.prev = node
self.head.next = node
self.size += 1
def pop(self, node=None):
if self.size == 0: return
if not node: node = self.tail.prev
node.prev.next = node.next
node.next.prev = node.prev
self.size -= 1
return node
class LFUCache:
def __init__(self, capacity: int):
self.size = 0
self.cap = capacity
self.min_freq = 0
self.cache = {}
self.freq = collections.defaultdict(DLinkedList)
def _update(self, node):
freq = node.freq
self.freq[freq].pop(node)
if self.min_freq == freq and not self.freq[freq]:
self.min_freq += 1
node.freq += 1
self.freq[node.freq].append(node)
def get(self, key: int) -> int:
if key not in self.cache: return -1
node = self.cache[key]
self._update(node)
return node.val
def put(self, key: int, value: int) -> None:
if self.cap == 0: return
if key in self.cache:
node = self.cache[key]
self._update(node)
node.val = value
else:
if self.size == self.cap:
node = self.freq[self.min_freq].pop()
del self.cache[node.key]
self.size -= 1
node = Node(key, value)
self.cache[key] = node
self.freq[1].append(node)
self.min_freq = 1
self.size += 1