LRU Cache medium
Problem Statement
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 thekey
if the key exists, otherwise return-1
.void put(int key, int value)
Update the value of thekey
if the key exists. Otherwise, add thekey-value
pair to the cache. If the number of keys exceeds thecapacity
from 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 cache = new LRUCache(2);
cache.put(1, 1); // cache is {1=1}
cache.put(2, 2); // cache is {1=1, 2=2}
cache.get(1); // return 1
cache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}
cache.get(1); // return -1 (not found)
cache.get(3); // return 3
cache.get(4); // return 4
Example 2
Input: ["LRUCache", "put", "get", "put", "put", "get", "get"] [[2], [2, 1], [2], [3, 2], [4, 1], [2], [3]] Output: [null, null, 1, null, null, -1, 2]
Explanation:
LRUCache cache = new LRUCache(2);
cache.put(2, 1); // cache is {2=1}
cache.get(2); // return 1, now {2=1}
cache.put(3, 2); // cache is {2=1, 3=2}
cache.put(4, 1); // cache is {3=2, 4=1} (evicts key 2)
cache.get(2); // return -1 (not found)
cache.get(3); // return 2
Steps
To implement an LRU cache efficiently, we'll use a combination of a HashMap
and a DoublyLinkedList
.
- HashMap: Stores key-value pairs for O(1) access time. The key will map to a Node in the DoublyLinkedList.
- DoublyLinkedList: Maintains the order of usage. The most recently used item is at the head, and the least recently used is at the tail. We need a doubly linked list because we need to be able to remove nodes from the middle efficiently (in O(1) time).
For each get
and put
operation:
-
get(key)
:- Check if the key exists in the HashMap. If not, return -1.
- If it exists, get the corresponding node from the linked list.
- Move this node to the head of the linked list (marking it as most recently used).
- Return the value.
-
put(key, value)
:- Check if the key exists in the HashMap. If it does, update the value and move the node to the head of the linked list.
- If the key doesn't exist:
- If the cache is full, remove the tail node (LRU) from both the HashMap and the linked list.
- Add the new node to the head of the linked list.
- Add the key-node pair to the HashMap.
Explanation
The use of a HashMap and DoublyLinkedList provides the O(1) time complexity required. The HashMap allows for instant lookup of keys, while the DoublyLinkedList efficiently manages the order of usage, allowing for quick removal of the LRU element. Moving nodes in the linked list is also O(1) because we maintain references to the head and tail.
Code
import java.util.HashMap;
class Node {
int key;
int value;
Node prev;
Node next;
Node(int key, int value) {
this.key = key;
this.value = value;
}
}
class LRUCache {
private int capacity;
private HashMap<Integer, Node> map;
private Node head;
private Node tail;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
head = new Node(0, 0); // Dummy head
tail = new Node(0, 0); // Dummy tail
head.next = tail;
tail.prev = head;
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
Node node = map.get(key);
remove(node);
add(node);
return node.value;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
Node node = map.get(key);
node.value = value;
remove(node);
add(node);
} else {
if (map.size() == capacity) {
remove(tail.prev);
}
Node node = new Node(key, value);
add(node);
map.put(key, node);
}
}
private void add(Node node) {
node.next = head.next;
head.next.prev = node;
head.next = node;
node.prev = head;
}
private void remove(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
map.remove(node.key);
}
}
Complexity
- Time Complexity:
get(key)
: O(1)put(key, value)
: O(1)
- Space Complexity: O(capacity) – proportional to the cache size.