LRU cache design with hash map and doubly linked list in Data Structures Theory - Time & Space Complexity
Analyzing time complexity helps us understand how fast the LRU cache operations run as the cache size grows.
We want to know how the time to get or put items changes when the cache holds more entries.
Analyze the time complexity of the following LRU cache operations.
class Node:
def __init__(self, key, value):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity):
self.cache = {} # key to node
self.capacity = capacity
self.head = Node(0, 0) # dummy head
self.tail = Node(0, 0) # dummy tail
self.head.next = self.tail
self.tail.prev = self.head
def get(self, key):
if key in self.cache:
node = self.cache[key]
self._remove(node)
self._add(node)
return node.value
return -1
def put(self, key, value):
if key in self.cache:
self._remove(self.cache[key])
node = Node(key, value)
self._add(node)
self.cache[key] = node
if len(self.cache) > self.capacity:
lru = self.head.next
self._remove(lru)
del self.cache[lru.key]
def _remove(self, node):
prev, nxt = node.prev, node.next
prev.next = nxt
nxt.prev = prev
def _add(self, node):
prev, nxt = self.tail.prev, self.tail
prev.next = node
node.prev = prev
node.next = nxt
nxt.prev = node
This code keeps track of recently used items with a hash map and a doubly linked list to quickly update usage order.
Look at the main operations that repeat when we get or put items.
- Primary operation: Accessing the hash map to find nodes and updating the doubly linked list pointers.
- How many times: Each get or put does a fixed number of these steps, regardless of cache size.
Each operation touches only a few nodes and hash map entries, no matter how big the cache is.
| Input Size (cache capacity) | Approx. Operations per get/put |
|---|---|
| 10 | About 5-10 pointer and hash map steps |
| 100 | Still about 5-10 steps |
| 1000 | Still about 5-10 steps |
Pattern observation: The number of steps stays roughly the same no matter how many items the cache holds.
Time Complexity: O(1)
This means each get or put operation takes the same short time, no matter how many items are stored.
[X] Wrong: "Since the cache stores many items, get or put must take longer as it grows."
[OK] Correct: The hash map lets us find items instantly, and the doubly linked list lets us update usage order quickly, so operations stay fast.
Understanding this design shows you can combine data structures to keep operations efficient, a key skill in real-world coding.
"What if we replaced the doubly linked list with a singly linked list? How would the time complexity change?"
Practice
Solution
Step 1: Understand the role of doubly linked list in LRU cache
The doubly linked list maintains the order of items from most recently used to least recently used.Step 2: Explain why order tracking is important
This order allows quick removal of the least recently used item when the cache is full.Final Answer:
To keep track of the order of item usage for quick updates -> Option DQuick Check:
Doubly linked list = usage order tracking [OK]
- Confusing hash map role with doubly linked list
- Thinking doubly linked list stores key-value pairs
- Assuming it sorts items by value
get operation in an LRU cache implemented with a hash map and doubly linked list?Solution
Step 1: Analyze the
The hash map provides direct access to the node in constant time.getoperation componentsStep 2: Consider the doubly linked list update
Moving the accessed node to the front is done in constant time using pointers.Final Answer:
O(1), constant time -> Option BQuick Check:
Hash map + doubly linked list = O(1) get [OK]
- Assuming list traversal causes O(n) time
- Confusing sorting with LRU operations
- Thinking get requires scanning all items
put(1, 1), put(2, 2), get(1), put(3, 3), what will get(2) return?Solution
Step 1: Track cache state after each operation
Afterput(1,1)andput(2,2), cache has keys 1 and 2.get(1)makes key 1 most recently used.Step 2: Insert
Key 2 is least recently used and gets evicted. Cache now has keys 1 and 3.put(3,3)and evict least recently usedStep 3: Check
Key 2 was evicted, soget(2)resultget(2)returns -1.Final Answer:
-1 -> Option CQuick Check:
Evicted key 2, so get(2) = -1 [OK]
- Forgetting eviction after capacity reached
- Assuming get updates usage but doesn't evict
- Returning wrong value for evicted key
Solution
Step 1: Understand eviction role in LRU cache
Eviction removes the least recently used item when capacity is exceeded.Step 2: Identify why eviction might not happen
If eviction logic is missing or not triggered, items will never be removed despite capacity limits.Final Answer:
The eviction logic is missing or not triggered when capacity is exceeded -> Option AQuick Check:
Missing eviction = no item removal [OK]
- Blaming hash map updates for eviction issues
- Ignoring eviction step in put operation
- Assuming capacity zero means no eviction
peekLeastUsed() operation that returns the least recently used key without removing it. Which data structure property allows this operation to be efficient?Solution
Step 1: Identify how least recently used item is tracked
The doubly linked list keeps most recently used at head and least recently used at tail.Step 2: Explain how to access least used without removal
Using the tail pointer, we can directly access the least recently used key efficiently.Final Answer:
The tail pointer of the doubly linked list points to the least recently used item -> Option AQuick Check:
Tail pointer = least recently used access [OK]
- Assuming hash map stores sorted keys
- Thinking singly linked list is used
- Confusing priority queue with LRU structure
Ready to prove it?
Pass the test and this pattern moves unknown
