- The DSA Woodshed
- Algorithms
- Linked Lists
- LRU Cache
LRU Cache
Problem
Design a data structure that follows the Least Recently Used (LRU) eviction policy, supporting get and put in O(1) time.
Approach
OrderedDict version: move accessed keys to end; pop from front on evict. Manual version: doubly linked list + hash map for O(1) removal/insertion.
When to Use
O(1) cache with eviction — "design LRU/LFU cache", bounded-memory caching with recency tracking. Pattern: hash map + doubly linked list.
Complexity
| Time | O(1) for both get and put |
| Space | O(capacity) |
Source
"""LRU Cache implementation.
Problem:
Design a data structure that follows the Least Recently Used (LRU)
eviction policy, supporting get and put in O(1) time.
Approach:
OrderedDict version: move accessed keys to end; pop from front on evict.
Manual version: doubly linked list + hash map for O(1) removal/insertion.
When to use:
O(1) cache with eviction — "design LRU/LFU cache", bounded-memory
caching with recency tracking. Pattern: hash map + doubly linked list.
Complexity:
Time: O(1) for both get and put
Space: O(capacity)
"""
from collections import OrderedDict
from dataclasses import dataclass, field
class LRUCache:
"""LRU Cache using OrderedDict."""
def __init__(self, capacity: int) -> None:
if capacity <= 0:
msg = f"capacity must be positive, got {capacity}"
raise ValueError(msg)
self._cache: OrderedDict[int, int] = OrderedDict()
self._capacity = capacity
def get(self, key: int) -> int:
"""Return value for key, or -1 if not found. Marks key as recently used."""
if key not in self._cache:
return -1
self._cache.move_to_end(key)
return self._cache[key]
def put(self, key: int, value: int) -> None:
"""Insert or update key-value pair. Evicts LRU entry if at capacity."""
if key in self._cache:
self._cache.move_to_end(key)
self._cache[key] = value
if len(self._cache) > self._capacity:
self._cache.popitem(last=False)
# --- Manual doubly-linked-list implementation ---
@dataclass
class _DNode:
"""Internal node for the doubly linked list."""
key: int = 0
val: int = 0
prev: _DNode | None = field(default=None, repr=False)
next: _DNode | None = field(default=None, repr=False)
class LRUCacheManual:
"""LRU Cache using a doubly linked list + hash map."""
def __init__(self, capacity: int) -> None:
if capacity <= 0:
msg = f"capacity must be positive, got {capacity}"
raise ValueError(msg)
self._capacity = capacity
self._cache: dict[int, _DNode] = {}
# Sentinel nodes simplify edge-case handling.
self._head = _DNode()
self._tail = _DNode()
self._head.next = self._tail
self._tail.prev = self._head
def _remove(self, node: _DNode) -> None:
"""Unlink a node from the doubly linked list."""
assert node.prev is not None
assert node.next is not None
node.prev.next = node.next
node.next.prev = node.prev
def _add_to_front(self, node: _DNode) -> None:
"""Insert a node right after the head sentinel (most recent)."""
assert self._head.next is not None
node.next = self._head.next
node.prev = self._head
self._head.next.prev = node
self._head.next = node
def get(self, key: int) -> int:
"""Return value for key, or -1 if not found."""
if key not in self._cache:
return -1
node = self._cache[key]
self._remove(node)
self._add_to_front(node)
return node.val
def put(self, key: int, value: int) -> None:
"""Insert or update key-value pair. Evicts LRU entry if at capacity."""
if key in self._cache:
self._remove(self._cache[key])
node = _DNode(key, value)
self._cache[key] = node
self._add_to_front(node)
if len(self._cache) > self._capacity:
assert self._tail.prev is not None
lru = self._tail.prev
self._remove(lru)
del self._cache[lru.key]This page lives in git. Anyone can propose an edit. Edit this page View source