InterviewPrepKit

Home / Coding / Linked List

LRU Cache

medium Original β†—
Solving tips
  • Two requirements pull in different directions: O(1) lookup (hash map key -> node) and O(1) reorder-by-recency (doubly linked list); pair them.
  • Keep most-recently-used at one end, least at the other; use sentinel head/tail nodes to kill all empty/one-element edge cases.
  • A get hit must move the node to the recent end, and put on an existing key refreshes recency WITHOUT evicting; insert first, then check len > capacity.
  • On eviction, also delete the key from the map or it grows unbounded; in Python, collections.OrderedDict (move_to_end + popitem(last=False)) gives all this for free at O(1) per op.

Problem

Design a data structure that behaves like a fixed-size cache with a Least Recently Used (LRU) eviction policy. It is created with a positive capacity and supports two operations:

  • get(key) β€” return the value stored for key, or -1 if key is not present. A successful get counts as using the key.
  • put(key, value) β€” insert or overwrite the value for key. Inserting or overwriting also counts as using the key. If adding a new key would push the number of stored entries above capacity, first evict the key that was used least recently.

Both operations must run in O(1) average time.

β€œLeast recently used” means: among all keys currently in the cache, the one whose most recent get/put happened longest ago.

Examples

Example 1

LRUCache cache = new LRUCache(2)
put(1, 1)      // cache = {1=1}
put(2, 2)      // cache = {1=1, 2=2}
get(1)   -> 1  // 1 is now most-recently-used; order: 2 (old) ... 1 (new)
put(3, 3)      // capacity exceeded -> evict key 2; cache = {1=1, 3=3}
get(2)   -> -1 // 2 was evicted
put(4, 4)      // evict key 1; cache = {3=3, 4=4}
get(1)   -> -1
get(3)   -> 3
get(4)   -> 4

Example 2

LRUCache cache = new LRUCache(1)
put(1, 10)      // cache = {1=10}
put(2, 20)      // capacity 1 -> evict 1; cache = {2=20}
get(1)   -> -1
get(2)   -> 20

Overwriting an existing key never evicts, but it does refresh recency:

put(2, 99)      // cache = {2=99}, still size 1, no eviction
get(2)   -> 99

Constraints

  • 1 <= capacity <= 3000
  • 0 <= key <= 10^4, 0 <= value <= 10^5
  • Up to 2 * 10^5 calls total to get and put.
  • Every get and put must be O(1) on average β€” this rules out scanning the cache to find the least-recently-used entry.

Think about it first

Hint 1 You need two things fast: look up a key's value in O(1), and know the ordering from most- to least-recently-used so you can evict the right entry. A single array or dict alone gives you one but not the other.
Hint 2 Combine a hash map (key β†’ node) with a doubly linked list that keeps nodes ordered by recency: most-recently-used at one end, least-recently-used at the other. Moving a node to the "recent" end and popping from the "old" end are both O(1) when you have direct node pointers.
Hint 3 Use sentinel `head` and `tail` nodes so you never special-case an empty list. On `get`: unlink the node and re-insert it next to `head`. On `put`: if the key exists, update and move to front; otherwise create a node, insert at front, and if over capacity remove the node before `tail` and delete its key from the map. Python's `collections.OrderedDict` (or a plain dict, which preserves insertion order) can do all of this for you.
Your workspace Not runnable by design β€” this is your interview scratchpad. Saved on this device.