150NeetCode 150

43.LRU Cache

MediumLinked List · 9/11

Problem

Design a data structure for a Least Recently Used (LRU) cache with a fixed capacity:

  • LRUCache(int capacity) — initialise the cache with a positive capacity.
  • int get(int key) — return the value of key if present, otherwise -1. A successful get counts as a use.
  • void put(int key, int value) — insert or update key. If the insert pushes the size above capacity, evict the least recently used key.

Both get and put must run in O(1) average time.

Examples

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]
Explanation:
put(1,1), put(2,2)       cache (LRU -> MRU): 1, 2
get(1) = 1               cache: 2, 1
put(3,3) evicts key 2    cache: 1, 3
get(2) = -1
put(4,4) evicts key 1    cache: 3, 4
get(1) = -1, get(3) = 3, get(4) = 4
Input:
["LRUCache", "put", "put", "put", "get"]
[[1], [5, 50], [5, 55], [6, 60], [5]]
Output:
[null, null, null, null, -1]
Explanation: Updating key 5 does not grow the cache; inserting 6 evicts 5.

Constraints

  • 1 <= capacity <= 3000
  • 0 <= key <= 10^4
  • 0 <= value <= 10^5
  • At most 2 * 10^5 calls will be made to get and put.

💡 Key Insight

We need two things in O(1): find a key (a HashMap) and reorder / evict by recency (a list where we can move any node to the front and drop the back). A doubly linked list can unlink any node in O(1) if we already hold a reference to it — and the hash map gives us exactly that reference. Map key → list node; keep the most recent node next to the head sentinel and evict from the tail sentinel.

Brute Force (List Scan)

TimeO(capacity) per operationSpaceO(capacity)

Intuition

Keep [key, value] pairs in an ArrayList ordered from least to most recently used. Every operation scans for the key; "using" an entry means moving it to the end; evicting means removing index 0.

Algorithm

  1. get(key): scan for key; if found, remove it, append it to the end, and return its value; else -1.
  2. put(key, value): if present, remove it. If the list is at capacity, remove index 0. Append [key, value].

Visualization

capacity 2
put(1,1): [(1,1)]
put(2,2): [(1,1), (2,2)]
get(1):   [(2,2), (1,1)]          -> 1
put(3,3): full, drop index 0 -> [(1,1), (3,3)]

Code

java
import java.util.ArrayList;
import java.util.List;

class LRUCache {
    private final int capacity;
    private final List<int[]> entries = new ArrayList<>(); // index 0 = least recent

    public LRUCache(int capacity) {
        this.capacity = capacity;
    }

    public int get(int key) {
        for (int i = 0; i < entries.size(); i++) {
            if (entries.get(i)[0] == key) {
                int[] entry = entries.remove(i);
                entries.add(entry); // mark as most recently used
                return entry[1];
            }
        }
        return -1;
    }

    public void put(int key, int value) {
        for (int i = 0; i < entries.size(); i++) {
            if (entries.get(i)[0] == key) {
                entries.remove(i);
                break;
            }
        }
        if (entries.size() == capacity) {
            entries.remove(0); // evict least recently used
        }
        entries.add(new int[] {key, value});
    }
}

Complexity

  • Time: O(capacity) per operation — linear scan plus ArrayList.remove shifting.
  • Space: O(capacity) — one entry per cached key.

Complexity Comparison

Approach Time (per op) Space
List Scan O(capacity) O(capacity)
LinkedHashMap O(1) O(capacity)
HashMap + Doubly Linked List O(1) O(capacity)

Common Pitfalls

  • Forgetting that get also updates recency — only moving nodes on put gives wrong evictions.
  • Not storing the key inside the node — when evicting tail.prev you need its key to remove it from the map.
  • Evicting before checking whether the key already exists: updating an existing key must never evict anything.
  • Using a singly linked list — removing an arbitrary node then needs its predecessor, which costs O(n).
  • Skipping sentinels and then mishandling null when the list has zero or one node.