LRU Cache
Problem
Design and implement a data structure for a Least Recently Used (LRU) cache.
Implement the `LRUCache` class:
- `LRUCache(int capacity)` Initialize the LRU cache with positive size capacity.
- `int get(int key)` Return the value of the key if it exists, otherwise return -1.
- `void put(int key, int value)` Update the value if the key exists. Otherwise, add the key-value pair. If the number of keys exceeds capacity, evict the least recently used key.
Both get and put must run in O(1) average time complexity.
Common follow-ups
- How would you make this thread-safe?
- What if capacity is very large — any memory concerns?
Step-by-step study guide
Step 1: Clarify the operations
You need O(1) `get` and `put`. `get` should mark a key as recently used. `put` inserts or updates, then evicts the oldest key if size exceeds capacity.
Ask: Are keys and values integers only? Is capacity always positive? What to return when a key is missing?
Step 2: Trace examples
Capacity = 2:
- `put(1, 1)` → cache: {1=1}
- `put(2, 2)` → cache: {1=1, 2=2}
- `get(1)` → returns 1; key 1 is now most recent
- `put(3, 3)` → evict key 2 (LRU); cache: {1=1, 3=3}
- `get(2)` → returns -1
Step 3: Brute force
Store pairs in an array. On `get`, scan for the key O(n), move it to the end to mark recent O(n). On `put`, scan/update O(n), shift on eviction O(n). Total: O(n) per operation — too slow.
Step 4: Identify the pattern
You need two things at once:
- O(1) lookup by key → hash map
- O(1) reorder by recency → doubly linked list
The map stores key → list node. List order = usage order (head = most recent, tail = LRU).
Step 5: Design the optimized structure
Use dummy head and tail nodes to avoid null checks when splicing.
- `get(key)`: if missing return -1. Otherwise move node to front (remove + insert at head), return value.
- `put(key, value)`: if key exists, remove old node. Create node, insert at head, update map. If size > capacity, remove tail.prev node from list and map.
Step 6: Implement carefully
Helper methods `_remove(node)` and `_insert(node)` keep get/put readable. Always update both the list and the map on eviction.
Test: capacity 1 (every put evicts), repeated get on same key, update existing key without changing size.
Step 7: Complexity and follow-ups
- Time: O(1) average for get and put (hash map + constant-time list splices)
- Space: O(capacity)
Thread safety: wrap map + list in a lock, or use concurrent hash map with careful ordering.
Memory at scale: bounded by capacity; consider TTL or external store for very large caches.
Solution
# Solution locked # Sign in to unlock expert solutions # with multi-language code and analysis
Sign in to unlock
Create a free account to preview problems. Subscribe for full access to our curated bank — expert tutorials, follow-ups, and practice tools you won't find on public sites.