--- title: '146. LRU Cache' description: Design a data structure that follows the constraints of a Least Recently Used (LRU) cache icon: dot topics: - { name: "Hash Table", slug: "hash-table" } - { name: "Linked List", slug: "linked-list" } - { name: "Design", slug: "design" } - { name: "Doubly-Linked List", slug: "doubly-linked-list" } video: "7ABFKPK2hD4" issue: "https://github.com/prdlk/leetcode/issues/87" sidebar: label: 'LRU Cache' badge: 'Medium' --- ### Example 1: - Input: `` - Output: `` - Explanation: Input ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] `[[2]`, [1, 1], [2, 2], `[1]`, [3, 3], `[2]`, [4, 4], `[1]`, `[3]`, [4]] Output [null, null, null, `1`, null, `-1`, null, `-1`, `3`, 4] Explanation LRUCache lRUCache = new LRUCache(2); lRUCache.put(1, `1`); // cache is {1=1} lRUCache.put(2, `2`); // cache is {1=1, 2=2} lRUCache.get(1); // return `1` lRUCache.put(3, `3`); // LRU `key` was `2`, evicts `key 2`, cache is {1=1, 3=3} lRUCache.get(2); // returns `-1` (not found) lRUCache.put(4, `4`); // LRU `key` was `1`, evicts `key 1`, cache is {4=4, 3=3} lRUCache.get(1); // return `-1` (not found) lRUCache.get(3); // return `3` lRUCache.get(4); // return `4` ### Constraints: - `1 <= capacity <= 3000` - `0 <= key <= 10^4` - `0 <= value <= 10^5` - At most 2 * 10^5 calls will be made to get and put. ## Approach ```mermaid flowchart TD subgraph state["state — dict for O(1) lookup, doubly linked list for O(1) reorder"] H["head — least recent"] --- N["... nodes ..."] --- T["tail — most recent"] end subgraph get["get(key)"] G1{"key in cache?"} -- no --> G2(["return -1"]) G1 -- yes --> G3["_remove_node then _add_node — move it beside the tail"] G3 --> G4(["return node.value"]) end subgraph put["put(key, value)"] P1{"key in cache?"} -- yes --> P2["update value, _remove_node then _add_node"] P1 -- no --> P3["new Node, cache[key] = node, _add_node"] P3 --> P4{"len(cache) > capacity?"} P4 -- yes --> P5["evict head.next — the least recent — and del cache[lru.key]"] P4 -- no --> P6(["done"]) P2 --> P6 P5 --> P6 end state --> get state --> put ``` ## Solution ```py class Node: def __init__(self, key=0, value=0): self.key = key self.value = value self.prev = None self.next = None class LRUCache: def __init__(self, capacity: int): self.capacity = capacity self.cache = {} self.head = Node() self.tail = Node() self.head.next = self.tail # pyright: ignore[reportAttributeAccessIssue] self.tail.prev = self.head # pyright: ignore[reportAttributeAccessIssue] def _remove_node(self, node): prev_node = node.prev next_node = node.next prev_node.next = next_node next_node.prev = prev_node def _add_node(self, node): # Insert just before the tail (most recent at end) node.next = self.tail node.prev = self.tail.prev self.tail.prev.next = node # pyright: ignore[reportAttributeAccessIssue] self.tail.prev = node def get(self, key: int) -> int: if key not in self.cache: return -1 node = self.cache[key] self._remove_node(node) self._add_node(node) return node.value def put(self, key: int, value: int) -> None: if key in self.cache: node = self.cache[key] node.value = value self._remove_node(node) self._add_node(node) else: new_node = Node(key, value) self.cache[key] = new_node self._add_node(new_node) if len(self.cache) > self.capacity: lru = self.head.next # Oldest node is next to the head self._remove_node(lru) del self.cache[lru.key] # pyright: ignore[reportAttributeAccessIssue] # Your LRUCache object will be instantiated and called as such: # obj = LRUCache(capacity) # param_1 = obj.get(key) # obj.put(key,value) ```