π Core Idea
LFU (Least Frequently Used) means:
- Remove the least frequently used key
- If multiple keys have same frequency β remove least recently used (LRU)
π¦ Data Structures Used
We maintain:
-
key β node(for O(1) access) -
freq β doubly linked list(group nodes by frequency) -
minFreq(track minimum frequency in cache)
π§± Structure Visualization
Freq = 1 β [ most recent .... least recent ]
Freq = 2 β [ most recent .... least recent ]
Freq = 3 β [ most recent .... least recent ]
Each frequency has its own LRU list
π Example Walkthrough
Capacity = 2
Step 1: put(1,10)
Freq 1: [1]
minFreq = 1
Step 2: put(2,20)
Freq 1: 2, 1
minFreq = 1
Step 3: get(1)
- Access key
1 - Increase its frequency: 1 β 2
Freq 1: [2]
Freq 2: [1]
minFreq = 1
π We removed 1 from freq 1 and added to freq 2
Step 4: put(3,30)
Cache is FULL β eviction needed
- minFreq = 1
- Look at Freq 1 β [2]
π Remove 2
After insertion:
Freq 1: [3]
Freq 2: [1]
minFreq = 1
Step 5: get(3)
- Move
3from freq 1 β freq 2
Freq 1: []
Freq 2: [3, 1]
minFreq = 2 (freq 1 is empty now)
Step 6: put(4,40)
Cache full β eviction
- minFreq = 2
- Freq 2 β [3, 1]
π Remove LRU β 1
After insertion:
Freq 1: [4]
Freq 2: [3]
minFreq = 1
π₯ Key Observations
1. Why multiple lists?
- Different frequencies β cannot maintain in single list
- Each frequency maintains its own LRU order
2. Why minFreq?
- Helps directly find which list to evict from in O(1)
3. Why doubly linked list?
- O(1) removal
- Maintain LRU order inside same frequency
π§ Mental Model
Think of LFU like books:
- Shelf 1 β least used books
- Shelf 2 β moderately used
- Shelf 3 β most used
When removing:
- Pick least used shelf
- Remove oldest book from that shelf
π§© Mapping to Code
| Concept | Code |
|---|---|
| Increase frequency | updateFreq(node) |
| Remove LRU | list->tail->prev |
| Track minimum | minFreq |
| Insert new node | freq = 1 |
π¨ Common Mistake
When a frequency list becomes empty:
if(freq == minFreq && list is empty)
minFreq++;
β If you donβt update minFreq, eviction will break
Code
class LFUCache {
public:
class Node {
public:
int key, val, freq;
Node* prev;
Node* next;
Node(int k, int v) {
key = k;
val = v;
freq = 1;
}
};
class List {
public:
Node* head;
Node* tail;
int size;
List() {
head = new Node(-1, -1);
tail = new Node(-1, -1);
head->next = tail;
tail->prev = head;
size = 0;
}
void addFront(Node* node) {
Node* temp = head->next;
node->next = temp;
node->prev = head;
head->next = node;
temp->prev = node;
size++;
}
void removeNode(Node* node) {
Node* prevv = node->prev;
Node* nextt = node->next;
prevv->next = nextt;
nextt->prev = prevv;
size--;
}
};
int cap;
int minFreq;
unordered_map<int, Node*> keyNode; // key -> node
unordered_map<int, List*> freqList; // freq -> DLL
LFUCache(int capacity) {
cap = capacity;
minFreq = 0;
}
void updateFreq(Node* node) {
int freq = node->freq;
freqList[freq]->removeNode(node);
if (freq == minFreq && freqList[freq]->size == 0) {
minFreq++;
}
node->freq++;
if (freqList.find(node->freq) == freqList.end()) {
freqList[node->freq] = new List();
}
freqList[node->freq]->addFront(node);
}
int get(int key) {
if (keyNode.find(key) == keyNode.end()) return -1;
Node* node = keyNode[key];
updateFreq(node);
return node->val;
}
void put(int key, int value) {
if (cap == 0) return;
if (keyNode.find(key) != keyNode.end()) {
Node* node = keyNode[key];
node->val = value;
updateFreq(node);
return;
}
if (keyNode.size() == cap) {
List* list = freqList[minFreq];
Node* nodeToRemove = list->tail->prev;
keyNode.erase(nodeToRemove->key);
list->removeNode(nodeToRemove);
}
Node* newNode = new Node(key, value);
minFreq = 1;
if (freqList.find(1) == freqList.end()) {
freqList[1] = new List();
}
freqList[1]->addFront(newNode);
keyNode[key] = newNode;
}
};
Top comments (0)