Skip to content
AI360Xpert
Beta
Difficulty: MediumLinked List

LRU Cache

Problem in Plain English

Design a fixed-capacity cache that returns stored values by key and evicts the least recently used entry when full. Both reads and writes must run in constant average time.

Problem Statement

Build a cache with a positive entry limit capacity. LRUCache(capacity) creates an empty cache. get(key) returns the stored value, or -1 if the key is missing. put(key, value) replaces an existing value or adds a new entry. When a new entry would exceed the limit, remove the key used least recently.

A successful get, an update, and a new insertion all make that key most recently used; a failed get changes nothing. Both methods must take O(1)O(1) average time. In the operation-sequence output, the constructor and put produce null, while get produces its returned integer.

Constraints

  • 1≤capacity≤30001 \leq capacity \leq 3000.
  • 0≤key≤1040 \leq key \leq 10^4.
  • 0≤value≤1050 \leq value \leq 10^5.
  • At most 2×1052 \times 10^5 calls are made to get and put.

Examples

Example 1

Input
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
Output
[null, null, null, 1, null, -1, null, -1, 3, 4]

With capacity 2, get(1) returns 1, put(3, 3) evicts key 2, and put(4, 4) evicts key 1, leaving values 3 and 4.

Example 2

Input
["LRUCache", "get", "put", "put", "get", "put", "get", "get"] [[1], [0], [0, 0], [0, 100000], [0], [10000, 7], [0], [10000]]
Output
[null, -1, null, null, 100000, null, -1, 7]

With capacity 1, the empty read misses. Writing key 0 twice updates its value without evicting it or adding another entry; its value becomes 100000. Inserting key 10000 then evicts 0, so the final reads return -1 and 7. Zero is a valid key and value.

Example 3

Input
["LRUCache", "put", "put", "put", "get", "put", "get", "get", "get", "put", "get"] [[2], [1, 10], [2, 20], [1, 11], [9], [3, 30], [2], [1], [3], [2, 22], [1]]
Output
[null, null, null, null, -1, null, -1, 11, 30, null, -1]

Updating key 1 refreshes it, making 2 oldest. The miss on 9 changes nothing, so adding 3 evicts 2. Reading 1 then 3 makes 1 oldest again. Reinserting previously evicted key 2 creates a fresh entry with value 22 and evicts 1.

Intuition

A cache needs both a key-to-value lookup and a memory of which entry was touched last. Least recently used means the entry whose latest successful get or put happened earliest. In Example 1, reading key 1 moves it ahead of key 2, so inserting key 3 must evict 2 even though 1 was inserted first. A miss has no stored entry to refresh. A map supplies lookup, while a doubly linked list supplies recency: each node has links to both neighbors, so a known node can be removed and moved without scanning.

Approaches

Array List Scan

Solution Details

Reveal Array List Scan: intuition, complexity, and code

Hints

Hint 1
Think of recency as an ordering of distinct keys. Which end should lose an entry, and which operations should move an entry to the other end?
Hint 2
An array can represent that order, but finding and removing an interior entry costs linear time. How can you retain a direct reference to the entry?
Hint 3
Map each key to a doubly linked node. Use two guards, refresh successful accesses at the head, and remove the node before the tail when over capacity.

Edge Cases

  • Capacity 1: an update keeps the sole key, but inserting a distinct key evicts it; the same guard rewiring handles both.
  • Getting from an empty cache or requesting an evicted key returns -1 without changing recency.
  • A successful get must refresh even when it returns value 0; use key presence rather than value truthiness.
  • Updating the least recent key changes its value and makes it newest without evicting another key.
  • Repeated gets of the already newest node still work: detach first and reinsert, avoiding self-links.
  • Reinserting an evicted key creates a fresh entry, and every insertion keeps the map and list synchronized.

Common Mistakes and Interview Tips

  • Treating insertion order as usage order: get(1) in Example 1 must protect key 1 and make key 2 the next victim.
  • Evicting on every put when full, even if the key already exists. An update does not grow the number of entries.
  • Removing a node from the list but leaving its map entry allows a later get to resurrect an evicted key.
  • Storing only values in nodes leaves eviction unable to identify the map key to delete.
  • Reassigning head.next before saving the old neighbor, or fixing only one neighbor link, can corrupt the list. Detach and insert must maintain both directions.
  • Failing to refresh successful gets or updates, or refreshing a miss, changes which key the next insertion evicts.
  • Calling hash-map operations worst-case constant time ignores collisions and resizing; the required bound is average, with an amortized insertion qualification.

Key Takeaway

When ordering and lookup must both be instant, map keys to doubly linked nodes so each structure covers the weakness of the other.