LRU Cache with O(1) Operations

hardPython

Lesson

LRU Cache: Combining Hash Maps and Doubly Linked Lists

An LRU (Least Recently Used) cache is a memory management strategy that evicts the least recently accessed item when storage is full. The challenge is achieving O(1) time complexity for both lookups and updates.

The Problem with Single Data Structures

A hash map gives us O(1) lookups but doesn't track access order. A linked list tracks order but requires O(n) time to find specific elements. The solution is combining both structures.

The Hybrid Approach

The key insight is using a doubly linked list to maintain access order, while a hash map provides direct node access. The hash map stores key -> node mappings, where each node contains the actual key-value pair plus prev/next pointers.

When an item is accessed:

  1. Hash map finds the node in O(1)
  2. Node is moved to the front of the list in O(1) (thanks to prev/next pointers)
  3. The front represents "most recently used", the back represents "least recently used"

When adding new items at capacity:

  1. Remove the tail node (LRU) from both list and hash map in O(1)
  2. Add new node to front of list and hash map in O(1)

Why Doubly Linked?

Singly linked lists require O(n) time to remove arbitrary nodes because you need the previous node. Doubly linked lists store both directions, enabling O(1) removal from anywhere in the list.

Implementation Pattern

The elegant approach uses dummy head/tail nodes to eliminate edge cases. Instead of checking "is this the first/last node?", you always insert after head and remove before tail. This simplifies the logic significantly.

Example
1class Node: 2 def __init__(self, key, value): 3 self.key = key 4 self.value = value 5 self.prev = None 6 self.next = None 7 8class SimpleCache: 9 def __init__(self): 10 self.cache = {} # key -> node mapping 11 # Dummy nodes eliminate edge cases 12 self.head = Node(None, None) 13 self.tail = Node(None, None) 14 self.head.next = self.tail 15 self.tail.prev = self.head 16 17 def move_to_front(self, node): 18 # Remove from current position 19 node.prev.next = node.next 20 node.next.prev = node.prev 21 22 # Insert after head 23 node.next = self.head.next 24 node.prev = self.head 25 self.head.next.prev = node 26 self.head.next = node
L10Hash map enables O(1) node lookup by key
L13Dummy head/tail eliminate null checks in insertion/removal
L18O(1) removal: update surrounding nodes' pointers
L22O(1) insertion: splice between head and head.next

Key Takeaways

  • •Combining hash maps with doubly linked lists enables O(1) operations on ordered data
  • •Dummy head/tail nodes eliminate edge cases and simplify pointer manipulation
  • •The pattern of "direct access + ordering" appears in many advanced data structures
Loading...