Skip to content

Commit 3e035f9

Browse files
authored
Merge pull request #72 from NEHA-AMIN/feature/lru-cache
Solution #146 - Neha Amin - 16/07/2025
2 parents 7442a3c + d98eb65 commit 3e035f9

File tree

4 files changed

+325
-0
lines changed

4 files changed

+325
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# 146. LRU Cache
2+
Difficulty: Medium
3+
Category: HashMap, Doubly Linked List, Design
4+
Leetcode Link: [Problem Link](https://leetcode.com/problems/lru-cache/)
5+
6+
---
7+
8+
## 📝 Introduction
9+
10+
Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.
11+
12+
Implement the LRUCache class:
13+
- LRUCache(int capacity) initializes the LRU cache with a positive size capacity.
14+
- int get(int key) returns the value of the key if the key exists, otherwise returns -1.
15+
- void put(int key, int value) updates the value of the key if it exists. Otherwise, adds the key-value pair to the cache. If the number of keys exceeds the capacity, evict the least recently used key.
16+
17+
Both get and put must run in O(1) average time complexity.
18+
19+
---
20+
21+
## 💡 Approach & Key Insights
22+
23+
To achieve O(1) time complexity for both get and put operations:
24+
- Use a HashMap to store key → pointer to node in a Doubly Linked List (DLL).
25+
- The DLL keeps track of the most recently used order, with:
26+
- Head: Most recently used.
27+
- Tail: Least recently used.
28+
- When a key is accessed or added:
29+
- Move its node to the front (right after head).
30+
- If capacity is exceeded, remove node from the end (before tail).
31+
32+
---
33+
34+
## 🛠️ Breakdown of Approaches
35+
36+
### 1️⃣ Brute Force / Naive Approach
37+
38+
Explanation:
39+
Use a LinkedList and linear search to implement put and get.
40+
Too slow for O(1) requirement. Every access or insert may require O(N) time for searching.
41+
42+
Time Complexity: O(N) per operation
43+
Space Complexity: O(N) for storage
44+
45+
Example/Dry Run:
46+
get(1) — Scan entire list to find key = 1 → O(N)
47+
48+
---
49+
50+
### 2️⃣ Optimized Approach (HashMap + DLL)
51+
52+
Explanation:
53+
Use:
54+
- HashMap<int, Node*> to access nodes in O(1).
55+
- Doubly Linked List to maintain LRU order.
56+
57+
Steps:
58+
- get(key):
59+
- If key exists → move node to head → return value.
60+
- Else → return -1.
61+
- put(key, value):
62+
- If key exists → update value → move node to head.
63+
- If not → insert new node → add to head.
64+
- If capacity exceeded → remove node from tail → erase from map.
65+
66+
Time Complexity: O(1) for both get and put
67+
Space Complexity: O(capacity)
68+
69+
Example/Dry Run:
70+
Input:
71+
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
72+
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
73+
Output: [null, null, null, 1, null, -1, null, -1, 3, 4]
74+
75+
---
76+
77+
### 3️⃣ Best / Final Optimized Approach (if applicable)
78+
79+
Same as optimized. No better than O(1) per operation.
80+
81+
---
82+
83+
## 📊 Complexity Analysis
84+
85+
| Approach | Time Complexity | Space Complexity |
86+
|----------------|------------------|------------------|
87+
| Brute Force | O(N) | O(N) |
88+
| Optimized | O(1) | O(N) |
89+
| Best Approach | O(1) | O(N) |
90+
91+
---
92+
93+
## 📉 Optimization Ideas
94+
95+
- Use a custom DLL instead of built-in collections for better control over node management.
96+
- Avoid unnecessary key re-insertion in hashmap.
97+
98+
---
99+
100+
## 📌 Example Walkthroughs & Dry Runs
101+
102+
Capacity = 2
103+
put(1,1) → cache = {1=1}
104+
put(2,2) → cache = {1=1, 2=2}
105+
get(1) → return 1 → cache = {2=2, 1=1}
106+
put(3,3) → evict 2 → cache = {1=1, 3=3}
107+
get(2) → return -1 (not found)
108+
put(4,4) → evict 1 → cache = {3=3, 4=4}
109+
get(1) → return -1 (not found)
110+
get(3) → return 3
111+
get(4) → return 4
112+
113+
---
114+
115+
116+
## 🔗 Additional Resources
117+
118+
- [Stack Visualization Tool](https://visualgo.net/en/list)
119+
120+
Author: Neha Amin
121+
Date: 19/07/2025
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
class LRUCache {
2+
public:
3+
class node {
4+
public:
5+
int key;
6+
int val;
7+
node *next;
8+
node *prev;
9+
node(int _key, int _val) {
10+
key = _key;
11+
val = _val;
12+
next = nullptr;
13+
prev = nullptr;
14+
}
15+
};
16+
17+
node *head = new node(-1, -1);
18+
node *tail = new node(-1, -1);
19+
20+
int cap;
21+
unordered_map<int, node*> m;
22+
23+
LRUCache(int capacity) {
24+
cap = capacity;
25+
head->next = tail;
26+
tail->prev = head;
27+
}
28+
29+
void addnode(node *newnode) {
30+
node *temp = head->next;
31+
newnode->next = temp;
32+
newnode->prev = head;
33+
head->next = newnode;
34+
temp->prev = newnode;
35+
}
36+
37+
void deletenode(node *delnode) {
38+
node *delprev = delnode->prev;
39+
node *delnext = delnode->next;
40+
delprev->next = delnext;
41+
delnext->prev = delprev;
42+
}
43+
44+
int get(int key_) {
45+
if (m.find(key_) != m.end()) {
46+
node *resnode = m[key_];
47+
int res = resnode->val;
48+
m.erase(key_);
49+
deletenode(resnode);
50+
addnode(resnode);
51+
m[key_] = head->next;
52+
return res;
53+
}
54+
return -1;
55+
}
56+
57+
void put(int key_, int value) {
58+
if (m.find(key_) != m.end()) {
59+
node *existingnode = m[key_];
60+
m.erase(key_);
61+
deletenode(existingnode);
62+
}
63+
if (m.size() == cap) {
64+
m.erase(tail->prev->key);
65+
deletenode(tail->prev);
66+
}
67+
addnode(new node(key_, value));
68+
m[key_] = head->next;
69+
}
70+
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
class LRUCache {
2+
class Node {
3+
int key;
4+
int val;
5+
Node next;
6+
Node prev;
7+
8+
Node(int key, int val) {
9+
this.key = key;
10+
this.val = val;
11+
this.next = null;
12+
this.prev = null;
13+
}
14+
}
15+
16+
Node head = new Node(-1, -1);
17+
Node tail = new Node(-1, -1);
18+
int cap;
19+
HashMap<Integer, Node> cache;
20+
21+
public LRUCache(int capacity) {
22+
cap = capacity;
23+
cache = new HashMap<>();
24+
head.next = tail;
25+
tail.prev = head;
26+
}
27+
28+
private void addNode(Node newNode) {
29+
Node temp = head.next;
30+
newNode.next = temp;
31+
newNode.prev = head;
32+
head.next = newNode;
33+
temp.prev = newNode;
34+
}
35+
36+
private void deleteNode(Node delNode) {
37+
Node delPrev = delNode.prev;
38+
Node delNext = delNode.next;
39+
delPrev.next = delNext;
40+
delNext.prev = delPrev;
41+
}
42+
43+
public int get(int key) {
44+
if (cache.containsKey(key)) {
45+
Node resNode = cache.get(key);
46+
int result = resNode.val;
47+
cache.remove(key);
48+
deleteNode(resNode);
49+
addNode(resNode);
50+
cache.put(key, head.next);
51+
return result;
52+
}
53+
return -1;
54+
}
55+
56+
public void put(int key, int value) {
57+
if (cache.containsKey(key)) {
58+
Node existingNode = cache.get(key);
59+
cache.remove(key);
60+
deleteNode(existingNode);
61+
}
62+
if (cache.size() == cap) {
63+
Node lruNode = tail.prev;
64+
cache.remove(lruNode.key);
65+
deleteNode(lruNode);
66+
}
67+
addNode(new Node(key, value));
68+
cache.put(key, head.next);
69+
}
70+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
class LRUCache:
2+
class Node:
3+
def __init__(self, key=0, val=0):
4+
self.key = key
5+
self.val = val
6+
self.next = None
7+
self.prev = None
8+
9+
def __init__(self, capacity: int):
10+
self.cap = capacity
11+
self.cache = {} # key -> node
12+
13+
# Create dummy head and tail nodes
14+
self.head = self.Node(-1, -1)
15+
self.tail = self.Node(-1, -1)
16+
self.head.next = self.tail
17+
self.tail.prev = self.head
18+
19+
def add_node(self, new_node):
20+
"""Add node right after head"""
21+
temp = self.head.next
22+
new_node.next = temp
23+
new_node.prev = self.head
24+
self.head.next = new_node
25+
temp.prev = new_node
26+
27+
def delete_node(self, del_node):
28+
"""Remove node from the list"""
29+
del_prev = del_node.prev
30+
del_next = del_node.next
31+
del_prev.next = del_next
32+
del_next.prev = del_prev
33+
34+
def get(self, key: int) -> int:
35+
if key in self.cache:
36+
res_node = self.cache[key]
37+
result = res_node.val
38+
39+
# Move to head (mark as recently used)
40+
del self.cache[key]
41+
self.delete_node(res_node)
42+
self.add_node(res_node)
43+
self.cache[key] = self.head.next
44+
45+
return result
46+
return -1
47+
48+
def put(self, key: int, value: int) -> None:
49+
if key in self.cache:
50+
# Update existing key
51+
existing_node = self.cache[key]
52+
del self.cache[key]
53+
self.delete_node(existing_node)
54+
55+
if len(self.cache) == self.cap:
56+
# Remove LRU (tail.prev)
57+
lru_node = self.tail.prev
58+
del self.cache[lru_node.key]
59+
self.delete_node(lru_node)
60+
61+
# Add new node
62+
new_node = self.Node(key, value)
63+
self.add_node(new_node)
64+
self.cache[key] = self.head.next

0 commit comments

Comments
 (0)