Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.
Implement the LRUCache class:
LRUCache(int capacity)Initialize the LRU cache with positive sizecapacity.int get(int key)Return the value of thekeyif the key exists, otherwise return-1.void put(int key, int value)Update the value of thekeyif thekeyexists. Otherwise, add thekey-valuepair to the cache. If the number of keys exceeds thecapacityfrom this operation, evict the least recently used key.
The functions get and put must each run in O(1) average time complexity.
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]
Explanation LRUCache lRUCache = new LRUCache(2); lRUCache.put(1, 1); // cache is {1=1} lRUCache.put(2, 2); // cache is {1=1, 2=2} lRUCache.get(1); // return 1 lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3} lRUCache.get(2); // returns -1 (not found) lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3} lRUCache.get(1); // return -1 (not found) lRUCache.get(3); // return 3 lRUCache.get(4); // return 4
Constraints:
1 <= capacity <= 30000 <= key <= 1040 <= value <= 105- At most
2 * 105calls will be made togetandput.
Approach: HashMap + Doubly Linked List
Algorithm
- Maintain a
HashMapmapping keys to list nodes for O(1) lookups - Maintain a doubly linked list where the head side holds the most recently used entry and the tail side holds the least recently used entry
get(key): if absent return -1; otherwise move the node to the head and return its valueput(key, value): if the key exists, update the value and move the node to the head; otherwise insert a new node at the head. If the size exceeds the capacity, remove the node at the tail and its map entry- Use dummy head and tail sentinels to simplify boundary handling
Time & Space Complexity
- Time Complexity: O(1) for both
getandput - Space Complexity: O(capacity) - the hash map and the doubly linked list store at most
capacitynodes
Java Implementation
import java.util.HashMap;
import java.util.Map;
public class LRUCache {
private static class Node {
int key;
int value;
Node prev;
Node next;
Node(int key, int value) {
this.key = key;
this.value = value;
}
}
private final int capacity;
private final Map<Integer, Node> map = new HashMap<>();
private final Node head = new Node(0, 0);
private final Node tail = new Node(0, 0);
public LRUCache(int capacity) {
this.capacity = capacity;
head.next = tail;
tail.prev = head;
}
public int get(int key) {
Node node = map.get(key);
if (node == null) {
return -1;
}
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
Node node = map.get(key);
if (node != null) {
node.value = value;
moveToHead(node);
return;
}
Node newNode = new Node(key, value);
map.put(key, newNode);
addToHead(newNode);
if (map.size() > capacity) {
Node last = tail.prev;
removeNode(last);
map.remove(last.key);
}
}
private void moveToHead(Node node) {
removeNode(node);
addToHead(node);
}
private void addToHead(Node node) {
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
private void removeNode(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
// Test method
public static void main(String[] args) {
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // cache is {1=1}
lRUCache.put(2, 2); // cache is {1=1, 2=2}
System.out.println(lRUCache.get(1)); // return 1
lRUCache.put(3, 3); // evicts key 2
System.out.println(lRUCache.get(2)); // return -1 (not found)
lRUCache.put(4, 4); // evicts key 1
System.out.println(lRUCache.get(1)); // return -1 (not found)
System.out.println(lRUCache.get(3)); // return 3
System.out.println(lRUCache.get(4)); // return 4
// Expected: 1, -1, -1, 3, 4
}
}
Example Walkthrough
With capacity 2:
put(1,1): add node 1 to the head. Order (MRU to LRU): 1put(2,2): add node 2 to the head. Order: 2, 1get(1): move 1 to the head. Order: 1, 2. Return 1put(3,3): add 3 to the head; size exceeds 2, evict tail node 2. Order: 3, 1get(2): not in the map, return -1put(4,4): add 4 to the head; evict tail node 1. Order: 4, 3get(1): -1;get(3): 3;get(4): 4
Key Points
- Doubly Linked List: Enables O(1) node removal and insertion at either end
- Hash Map: Maps key to node for O(1) access, and the node itself carries the key for eviction
- Sentinels: Dummy
headandtailavoid null checks on boundary operations - Recency Order: MRU at the head, LRU at the tail; every access moves the node to the head
- Eviction: When over capacity, remove the tail node and delete its map entry