🧠
Optimal Approach (Custom Doubly Linked List + Hash Map)
💡 This approach implements the LRU cache from scratch using a doubly linked list and a hash map to achieve O(1) operations without relying on built-in ordered dictionaries.
Intuition
Use a doubly linked list to maintain usage order and a hash map to access nodes in O(1). On get or put, move the node to the front. Evict from the tail when capacity is exceeded.
Algorithm
- Create a doubly linked list with dummy head and tail nodes to simplify edge cases.
- Maintain a hash map from keys to nodes in the list.
- For get(key), if key exists, move the node to the front and return its value; else return -1.
- For put(key, value), if key exists, update value and move node to front; else create new node at front.
- If capacity exceeded, remove node from tail and delete from hash map.
💡 The doubly linked list allows O(1) removal and insertion of nodes, and the hash map provides O(1) access to nodes by key.
class Node:
def __init__(self, key=0, val=0):
self.key = key
self.val = val
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.cache = {}
self.head = Node()
self.tail = Node()
self.head.next = self.tail
self.tail.prev = self.head
def _remove(self, node):
prev, nxt = node.prev, node.next
prev.next = nxt
nxt.prev = prev
def _add(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def get(self, key: int) -> int:
if key not in self.cache:
return -1
node = self.cache[key]
self._remove(node)
self._add(node)
return node.val
def put(self, key: int, value: int) -> None:
if key in self.cache:
self._remove(self.cache[key])
node = Node(key, value)
self._add(node)
self.cache[key] = node
if len(self.cache) > self.capacity:
lru = self.tail.prev
self._remove(lru)
del self.cache[lru.key]
# Driver code
if __name__ == '__main__':
cache = LRUCache(2)
print(cache.put(1, 1))
print(cache.put(2, 2))
print(cache.get(1))
print(cache.put(3, 3))
print(cache.get(2))
print(cache.put(4, 4))
print(cache.get(1))
print(cache.get(3))
print(cache.get(4))
Line Notes
class Node:Define node structure for doubly linked list with key, value, and pointers
self.head = Node()Dummy head node to simplify insertions at front
self.tail = Node()Dummy tail node to simplify removals at end
def _remove(self, node):Helper to unlink a node from the list in O(1) time
def _add(self, node):Helper to insert a node right after head (most recent position)
if key not in self.cache:Check if key exists for get operation to return -1 if missing
self._remove(node)Remove node before re-adding to update usage order
if len(self.cache) > self.capacity:Evict least recently used node when capacity exceeded
import java.util.*;
class LRUCache {
class Node {
int key, val;
Node prev, next;
Node(int k, int v) { key = k; val = v; }
}
private int capacity;
private Map<Integer, Node> cache;
private Node head, tail;
public LRUCache(int capacity) {
this.capacity = capacity;
cache = new HashMap<>();
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail;
tail.prev = head;
}
private void remove(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void add(Node node) {
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
public int get(int key) {
if (!cache.containsKey(key)) return -1;
Node node = cache.get(key);
remove(node);
add(node);
return node.val;
}
public void put(int key, int value) {
if (cache.containsKey(key)) {
remove(cache.get(key));
}
Node node = new Node(key, value);
add(node);
cache.put(key, node);
if (cache.size() > capacity) {
Node lru = tail.prev;
remove(lru);
cache.remove(lru.key);
}
}
public static void main(String[] args) {
LRUCache cache = new LRUCache(2);
cache.put(1, 1);
cache.put(2, 2);
System.out.println(cache.get(1)); // 1
cache.put(3, 3);
System.out.println(cache.get(2)); // -1
cache.put(4, 4);
System.out.println(cache.get(1)); // -1
System.out.println(cache.get(3)); // 3
System.out.println(cache.get(4)); // 4
}
}
Line Notes
class Node {Node class for doubly linked list with key, value, and pointers
head = new Node(0, 0);Dummy head node for easy insertion at front
tail = new Node(0, 0);Dummy tail node for easy removal at end
private void remove(Node node)Unlink node from list in O(1) time
private void add(Node node)Insert node right after head to mark as most recently used
if (!cache.containsKey(key)) return -1;Return -1 if key not found in cache
if (cache.size() > capacity)Evict least recently used node when over capacity
cache.remove(lru.key);Remove evicted key from map
#include <iostream>
#include <unordered_map>
using namespace std;
class LRUCache {
struct Node {
int key, val;
Node* prev;
Node* next;
Node(int k, int v) : key(k), val(v), prev(nullptr), next(nullptr) {}
};
int capacity;
unordered_map<int, Node*> cache;
Node* head;
Node* tail;
void remove(Node* node) {
node->prev->next = node->next;
node->next->prev = node->prev;
}
void add(Node* node) {
node->next = head->next;
node->prev = head;
head->next->prev = node;
head->next = node;
}
public:
LRUCache(int capacity) {
this->capacity = capacity;
head = new Node(0, 0);
tail = new Node(0, 0);
head->next = tail;
tail->prev = head;
}
int get(int key) {
if (cache.find(key) == cache.end()) return -1;
Node* node = cache[key];
remove(node);
add(node);
return node->val;
}
void put(int key, int value) {
if (cache.find(key) != cache.end()) {
remove(cache[key]);
delete cache[key];
}
Node* node = new Node(key, value);
add(node);
cache[key] = node;
if ((int)cache.size() > capacity) {
Node* lru = tail->prev;
remove(lru);
cache.erase(lru->key);
delete lru;
}
}
};
int main() {
LRUCache cache(2);
cache.put(1, 1);
cache.put(2, 2);
cout << cache.get(1) << "\n"; // 1
cache.put(3, 3);
cout << cache.get(2) << "\n"; // -1
cache.put(4, 4);
cout << cache.get(1) << "\n"; // -1
cout << cache.get(3) << "\n"; // 3
cout << cache.get(4) << "\n"; // 4
return 0;
}
Line Notes
struct Node {Node struct for doubly linked list with key, value, and pointers
head = new Node(0, 0);Dummy head node for easy insertion at front
tail = new Node(0, 0);Dummy tail node for easy removal at end
void remove(Node* node)Unlink node from list in O(1) time
void add(Node* node)Insert node right after head to mark as most recently used
if (cache.find(key) == cache.end()) return -1;Return -1 if key not found in cache
if ((int)cache.size() > capacity)Evict least recently used node when over capacity
cache.erase(lru->key);Remove evicted key from map
class Node {
constructor(key, val) {
this.key = key;
this.val = val;
this.prev = null;
this.next = null;
}
}
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
this.head = new Node(0, 0);
this.tail = new Node(0, 0);
this.head.next = this.tail;
this.tail.prev = this.head;
}
_remove(node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
_add(node) {
node.next = this.head.next;
node.prev = this.head;
this.head.next.prev = node;
this.head.next = node;
}
get(key) {
if (!this.cache.has(key)) return -1;
const node = this.cache.get(key);
this._remove(node);
this._add(node);
return node.val;
}
put(key, value) {
if (this.cache.has(key)) {
this._remove(this.cache.get(key));
}
const node = new Node(key, value);
this._add(node);
this.cache.set(key, node);
if (this.cache.size > this.capacity) {
const lru = this.tail.prev;
this._remove(lru);
this.cache.delete(lru.key);
}
}
}
// Driver code
const cache = new LRUCache(2);
console.log(cache.put(1, 1));
console.log(cache.put(2, 2));
console.log(cache.get(1));
console.log(cache.put(3, 3));
console.log(cache.get(2));
console.log(cache.put(4, 4));
console.log(cache.get(1));
console.log(cache.get(3));
console.log(cache.get(4));
Line Notes
class Node {Node class for doubly linked list with key, value, and pointers
this.head = new Node(0, 0);Dummy head node for easy insertion at front
this.tail = new Node(0, 0);Dummy tail node for easy removal at end
_remove(node) {Unlink node from list in O(1) time
_add(node) {Insert node right after head to mark as most recently used
if (!this.cache.has(key)) return -1;Return -1 if key not found in cache
if (this.cache.size > this.capacity)Evict least recently used node when over capacity
this.cache.delete(lru.key);Remove evicted key from map
TimeO(1) for get and put operations
SpaceO(capacity) for storing nodes and map entries
Doubly linked list allows O(1) insert and remove; hash map allows O(1) access to nodes.
💡 This approach is the most efficient and demonstrates mastery of data structures for interview coding.
Interview Verdict: Accepted / Optimal solution
This is the best approach to implement in interviews when built-in ordered dictionaries are not allowed or to show deep understanding.