Least Frequently Used (LFU) Cache Implementation
Last Updated :
02 Jan, 2025
Design a data structure for the Least Frequently Used (LFU) Cache.
LFU (Least Frequently Used) Cache is a caching algorithm where the least frequently accessed cache block is removed when the cache reaches its capacity. In LFU, we take into account how often a page is accessed and how recently it was accessed. If a page has a higher frequency than another, it cannot be removed, as it is accessed more frequently if multiple pages share the same frequency, the one that was accessed least recently (the Least Recently Used, or LRU page) is removed. This is typically handled using a FIFO (First-In-First-Out) method, where the oldest page among those with the same frequency is the one to be removed.
It should support the following operations:
- LFUCache (Capacity c): Initialize LFU cache with positive size capacity c.
- get(key) - Returns the value of the given key if it exists in the cache; otherwise, returns -1.
- put(key, value) - Inserts or updates the key-value pair in the cache. If the cache reaches capacity, remove the least frequently used item before adding the new item. If there is a tie between keys with the same frequency, the least recently used (LRU) key among them should be removed.
Example:
Input: [LFUCache cache = new LFUCache(2), put(1, 1) , put(2, 2) , get(1) , put(3, 3) , get(2), put(4, 4), get(3) , get(4), put(5, 5)]
Output: [1 , -1, -1, 4]
Explanation: The values mentioned in the output are the values returned by get operations.
- Initialize LFUCache class with capacity = 2.
- cache.put(1, 1): (key, pair) = (1, 1) inserts the key-value pair (1, 1).
- cache.put(2, 2): (key , pair) = (2, 2) inserts the key-value pair (2, 2).
- cache.get(1): The cache retrieves the value for key 1, which is 1. After accessing key 1, its frequency increases to 2.
- cache.put(3, 3): The cache is now full (capacity = 2). To insert the new key-value pair (3, 3), the least frequently used key must be removed. key 2 have a frequency of 1. As a result, key 2 (the least recently accessed key) is removed and key-value pair (3, 3) is inserted with frequency 1.
- cache.get(2): cache returns -1 indicating that key 2 is not found.
- cache.put(4, 4): key 3 is removed as it has frequency of 1 and key-value pair (4, 4) is inserted with frequency 1.
- cache.get(3): returns -1 (key 3 not found)
- cache.get(4):The cache retrieves the value for key 4, which is 4. After accessing key 4, its frequency increases to 2.
- cache.put(5, 5): key 1 and key 4 both have a frequency of 2 . Now, key 1 will be removed as key 4 is most recently used and key-value pair (5, 5) is inserted with frequency 1.
[Naive Approach - 1] Using an Array of Nodes
The idea is to implement LFU using an array to store nodes, where each node holds a key-value pair, frequency count and timestamp. The primary operations, get and put, are performed with linear time due to the need to search through the array. The size of the array will be equal to the given capacity of the cache.
- put(int key, int value)
- If the cache is full, find the node with the least frequency and replace this node with the new key and value. When there is a tie (i.e., two or more keys with the same frequency), the least recently used key would be replaced.
- else, simply add the new node to the end of the array with the timestamp of insertion and frequency value 1.
- Time Complexity: O(n) (because we might have to search for the least frequent node).
- get(int key)
- Search through the array for the node with the matching key.
- If found, update its timestamp and frequency and return its value , else return -1.
- Time Complexity: O(n) (because we might have to check every node).
We initialize an array of size equal to that of our cache. Here each data element stores extra information to mark with an access timestamp and frequency. Timestamp shows the time at which the key is stored and frequency is count of number of time the key is used. We will use frequency to find the least frequently used element and the timestamp to find out the least recently used element in case of tie between frequency of multiple elements.
C++
// C++ Program to implement LFU Cache
// using array
#include <bits/stdc++.h>
using namespace std;
struct Node {
int key, value;
int timeStamp, cnt;
Node(int key, int val, int timeStamp) {
this->key = key;
this->value = val;
this->cnt = 1;
this->timeStamp = timeStamp;
}
};
class LFUCache {
public:
int capacity;
int curSize;
int curTime;
vector<Node *> cacheList;
// Constructor to initialize values
LFUCache(int capacity) {
this->capacity = capacity;
curSize = 0;
curTime = 0;
cacheList.resize(capacity, nullptr);
}
// Function to get the key's value
int get(int key) {
curTime++;
for (int i = 0; i < capacity; i++) {
if (cacheList[i] != nullptr && cacheList[i]->key == key) {
cacheList[i]->cnt++;
cacheList[i]->timeStamp = curTime;
return cacheList[i]->value;
}
}
return -1;
}
// Function to put a key-value pair
void put(int key, int value) {
curTime++;
if (capacity == 0)
return;
for (int i = 0; i < capacity; i++) {
if (cacheList[i] != nullptr && cacheList[i]->key == key) {
cacheList[i]->value = value;
cacheList[i]->cnt++;
cacheList[i]->timeStamp = curTime;
return;
}
}
if (curSize < capacity) {
curSize++;
for (int i = 0; i < capacity; i++) {
if (cacheList[i] == nullptr) {
cacheList[i] = new Node(key, value, curTime);
return;
}
}
}
else {
int minCnt = INT_MAX, minTime = INT_MAX, minIndex = -1;
for (int i = 0; i < capacity; i++) {
if (cacheList[i]->cnt < minCnt ||
(cacheList[i]->cnt == minCnt && cacheList[i]->timeStamp < minTime)) {
minCnt = cacheList[i]->cnt;
minTime = cacheList[i]->timeStamp;
minIndex = i;
}
}
cacheList[minIndex] = new Node(key, value, curTime);
}
}
};
int main() {
LFUCache cache(2);
cache.put(1, 1);
cache.put(2, 2);
cout << cache.get(1) << " ";
cache.put(3, 3);
cout << cache.get(2) << " ";
cache.put(4, 4);
cout << cache.get(3) << " ";
cout << cache.get(4) << " ";
cache.put(5, 5);
return 0;
}
Java
// Java Program to implement LFU Cache
// using array
import java.util.*;
class Node {
int key, value;
int timeStamp, cnt;
Node(int key, int val, int timeStamp) {
this.key = key;
this.value = val;
this.cnt = 1;
this.timeStamp = timeStamp;
}
}
class LFUCache {
int capacity;
int curSize;
int curTime;
Node[] cacheList;
// Constructor to initialize values
LFUCache(int capacity) {
this.capacity = capacity;
curSize = 0;
curTime = 0;
cacheList = new Node[capacity];
}
// Function to get the key's value
int get(int key) {
curTime++;
for (int i = 0; i < capacity; i++) {
if (cacheList[i] != null
&& cacheList[i].key == key) {
cacheList[i].cnt++;
cacheList[i].timeStamp = curTime;
return cacheList[i].value;
}
}
return -1;
}
// Function to put a key-value pair
void put(int key, int value) {
curTime++;
if (capacity == 0)
return;
for (int i = 0; i < capacity; i++) {
if (cacheList[i] != null
&& cacheList[i].key == key) {
cacheList[i].value = value;
cacheList[i].cnt++;
cacheList[i].timeStamp = curTime;
return;
}
}
if (curSize < capacity) {
curSize++;
for (int i = 0; i < capacity; i++) {
if (cacheList[i] == null) {
cacheList[i]
= new Node(key, value, curTime);
return;
}
}
}
else {
int minCnt = Integer.MAX_VALUE;
int minTime = Integer.MAX_VALUE;
int minIndex = -1;
for (int i = 0; i < capacity; i++) {
if (cacheList[i].cnt < minCnt
|| (cacheList[i].cnt == minCnt
&& cacheList[i].timeStamp
< minTime)) {
minCnt = cacheList[i].cnt;
minTime = cacheList[i].timeStamp;
minIndex = i;
}
}
cacheList[minIndex]
= new Node(key, value, curTime);
}
}
}
class GfG {
public static void main(String[] args) {
LFUCache cache = new LFUCache(2);
cache.put(1, 1);
cache.put(2, 2);
System.out.print(cache.get(1) + " ");
cache.put(3, 3);
System.out.print(cache.get(2) + " ");
cache.put(4, 4);
System.out.print(cache.get(3) + " ");
System.out.print(cache.get(4) + " ");
cache.put(5, 5);
}
}
Python
# Python Program to implement LFU Cache
# using array
class Node:
def __init__(self, key, value, timeStamp):
self.key = key
self.value = value
self.cnt = 1
self.timeStamp = timeStamp
# LFU Cache class
class LFUCache:
# Constructor to initialize values
def __init__(self, capacity):
self.capacity = capacity
self.curSize = 0
self.curTime = 0
self.cacheList = [None] * capacity
# Function to get the key's value
def get(self, key):
self.curTime += 1
for i in range(self.capacity):
if self.cacheList[i] is not None and self.cacheList[i].key == key:
self.cacheList[i].cnt += 1
self.cacheList[i].timeStamp = self.curTime
return self.cacheList[i].value
return -1
# Function to put a key-value pair
def put(self, key, value):
self.curTime += 1
if self.capacity == 0:
return
for i in range(self.capacity):
if self.cacheList[i] is not None and self.cacheList[i].key == key:
self.cacheList[i].value = value
self.cacheList[i].cnt += 1
self.cacheList[i].timeStamp = self.curTime
return
if self.curSize < self.capacity:
self.curSize += 1
for i in range(self.capacity):
if self.cacheList[i] is None:
self.cacheList[i] = Node(key, value, self.curTime)
return
else:
minCnt = float('inf')
minTime = float('inf')
minIndex = -1
for i in range(self.capacity):
if self.cacheList[i].cnt < minCnt or (
self.cacheList[i].cnt == minCnt and self.cacheList[i].timeStamp < minTime
):
minCnt = self.cacheList[i].cnt
minTime = self.cacheList[i].timeStamp
minIndex = i
self.cacheList[minIndex] = Node(key, value, self.curTime)
if __name__ == "__main__":
cache = LFUCache(2)
cache.put(1, 1)
cache.put(2, 2)
print(cache.get(1), end=" ")
cache.put(3, 3)
print(cache.get(2), end=" ")
cache.put(4, 4)
print(cache.get(3), end=" ")
print(cache.get(4), end=" ")
cache.put(5, 5)
C#
// C# Program to implement LFU Cache
// using array
using System;
class Node {
public int key, value, timeStamp, cnt;
public Node(int key, int value, int timeStamp) {
this.key = key;
this.value = value;
this.cnt = 1;
this.timeStamp = timeStamp;
}
}
// LFU Cache class
class LFUCache {
private int capacity;
private int curSize;
private int curTime;
private Node[] cacheList;
// Constructor to initialize values
public LFUCache(int capacity) {
this.capacity = capacity;
curSize = 0;
curTime = 0;
cacheList = new Node[capacity];
}
// Function to get the key's value
public int Get(int key) {
curTime++;
for (int i = 0; i < capacity; i++) {
if (cacheList[i] != null &&
cacheList[i].key == key) {
cacheList[i].cnt++;
cacheList[i].timeStamp = curTime;
return cacheList[i].value;
}
}
return -1;
}
// Function to put a key-value pair
public void Put(int key, int value) {
curTime++;
if (capacity == 0) return;
for (int i = 0; i < capacity; i++) {
if (cacheList[i] != null &&
cacheList[i].key == key) {
cacheList[i].value = value;
cacheList[i].cnt++;
cacheList[i].timeStamp = curTime;
return;
}
}
if (curSize < capacity) {
curSize++;
for (int i = 0; i < capacity; i++) {
if (cacheList[i] == null) {
cacheList[i] = new Node(key, value, curTime);
return;
}
}
} else {
int minCnt = int.MaxValue;
int minTime = int.MaxValue;
int minIndex = -1;
for (int i = 0; i < capacity; i++) {
if (cacheList[i].cnt < minCnt ||
(cacheList[i].cnt == minCnt &&
cacheList[i].timeStamp < minTime)) {
minCnt = cacheList[i].cnt;
minTime = cacheList[i].timeStamp;
minIndex = i;
}
}
cacheList[minIndex] = new Node(key, value, curTime);
}
}
}
class GfG {
static void Main() {
LFUCache cache = new LFUCache(2);
cache.Put(1, 1);
cache.Put(2, 2);
Console.Write(cache.Get(1) + " ");
cache.Put(3, 3);
Console.Write(cache.Get(2) + " ");
cache.Put(4, 4);
Console.Write(cache.Get(3) + " ");
Console.Write(cache.Get(4) + " ");
cache.Put(5, 5);
}
}
JavaScript
// JavaScript Program to implement LFU Cache
// using array
class Node {
constructor(key, value, timeStamp) {
this.key = key;
this.value = value;
this.cnt = 1;
this.timeStamp = timeStamp;
}
}
// LFU Cache class
class LFUCache {
// Constructor to initialize values
constructor(capacity) {
this.capacity = capacity;
this.curSize = 0;
this.curTime = 0;
this.cacheList = new Array(capacity).fill(null);
}
// Function to get the key's value
get(key) {
this.curTime++;
for (let i = 0; i < this.capacity; i++) {
if (this.cacheList[i] !== null &&
this.cacheList[i].key === key) {
this.cacheList[i].cnt++;
this.cacheList[i].timeStamp = this.curTime;
return this.cacheList[i].value;
}
}
return -1;
}
// Function to put a key-value pair
put(key, value) {
this.curTime++;
if (this.capacity === 0) return;
for (let i = 0; i < this.capacity; i++) {
if (this.cacheList[i] !== null &&
this.cacheList[i].key === key) {
this.cacheList[i].value = value;
this.cacheList[i].cnt++;
this.cacheList[i].timeStamp = this.curTime;
return;
}
}
if (this.curSize < this.capacity) {
this.curSize++;
for (let i = 0; i < this.capacity; i++) {
if (this.cacheList[i] === null) {
this.cacheList[i] = new Node(key, value, this.curTime);
return;
}
}
} else {
let minCnt = Infinity, minTime = Infinity, minIndex = -1;
for (let i = 0; i < this.capacity; i++) {
if (
this.cacheList[i].cnt < minCnt ||
(this.cacheList[i].cnt === minCnt &&
this.cacheList[i].timeStamp < minTime)
) {
minCnt = this.cacheList[i].cnt;
minTime = this.cacheList[i].timeStamp;
minIndex = i;
}
}
this.cacheList[minIndex] = new Node(key, value, this.curTime);
}
}
}
const cache = new LFUCache(2);
cache.put(1, 1);
cache.put(2, 2);
console.log(cache.get(1) + " ");
cache.put(3, 3);
console.log(cache.get(2) + " ");
cache.put(4, 4);
console.log(cache.get(3) + " ");
console.log(cache.get(4) + " ");
cache.put(5, 5);
Time Complexity: O(n), for each get() and put() operations where n is capacity of cache.
Auxiliary Space: O(capacity)
[Naive Approach - 2] Using Singly Linked List
The approach to implement an LFU (Least Frequently Used) cache involves using a singly linked list to maintain the order of cache entries.
- get(int key): The cache searches for the node with the requested key by traversing the list from the head. If the key is found, update its timestamp and frequency and return its value , else return -1. This operation has a time complexity of O(n) because it may require scanning through the entire list.
- put(int key, int value): The cache inserts a new key-value pair at the end of the list if the cache has not reached its capacity. If the key already exists, the corresponding node is found and updated. When the cache reaches its capacity, the least frequently used element, is removed. When there is a tie (i.e., two or more keys with the same frequency), the least recently used node would be removed. The time complexity for this operation is also O(n) due to the traversal and reordering steps involved.
C++
// C++ Program to implement LFU Cache
// using singly linked list
#include<bits/stdc++.h>
using namespace std;
struct Node {
int key, value;
int timeStamp, cnt;
Node *next;
Node(int key, int val, int timeStamp) {
this->key = key;
this->value = val;
this->cnt = 1;
this->timeStamp = timeStamp;
this->next = nullptr;
}
};
//LFU Cache class
class LFUCache {
public:
int capacity;
int curSize;
int curTime;
Node* head;
// Constructor to initialize values
LFUCache(int capacity) {
this->capacity = capacity;
curSize = 0;
curTime = 0;
head = new Node(-1, -1, -1);
}
// Function to get the key's value
int get(int key) {
curTime++;
Node* temp = head;
while(temp->next != nullptr) {
if(temp->next->key == key) {
temp->next->cnt++;
temp->next->timeStamp = curTime;
return temp->next->value;
}
temp = temp->next;
}
return -1;
}
// Function to put a key-value pair
void put(int key, int value) {
curTime++;
if(capacity == 0) return;
Node* temp = head;
while(temp->next != nullptr) {
if(temp->next->key == key) {
temp->next->value = value;
temp->next->cnt++;
temp->next->timeStamp = curTime;
return;
}
temp = temp->next;
}
if(curSize < capacity) {
curSize++;
Node* temp = head;
while(temp->next != nullptr) {
temp = temp->next;
}
temp->next = new Node(key, value, curTime);
return;
}
else {
int minCnt = INT_MAX, minTime = INT_MAX;
Node* minNode = nullptr;
Node* temp = head;
while(temp->next != nullptr) {
if(temp->next->cnt < minCnt ||
(temp->next->cnt == minCnt &&
temp->next->timeStamp < minTime)) {
minCnt = temp->next->cnt;
minTime = temp->next->timeStamp;
minNode = temp;
}
temp = temp->next;
}
Node* delNode = minNode->next;
minNode->next = minNode->next->next;
delete delNode;
temp = head;
while(temp->next != nullptr) {
temp = temp->next;
}
temp->next = new Node(key, value, curTime);
}
}
};
int main(){
LFUCache cache(2);
cache.put(1, 1);
cache.put(2, 2);
cout << cache.get(1) << " ";
cache.put(3, 3);
cout << cache.get(2) << " ";
cache.put(4, 4);
cout << cache.get(3) << " ";
cout << cache.get(4) << " ";
cache.put(5, 5);
return 0;
}
Java
// Java Program to implement LFU Cache
// using singly linked list
import java.util.*;
class Node {
int key, value;
int timeStamp, cnt;
Node next;
Node(int key, int val, int timeStamp) {
this.key = key;
this.value = val;
this.cnt = 1;
this.timeStamp = timeStamp;
this.next = null;
}
}
class LFUCache {
int capacity;
int curSize;
int curTime;
Node head;
// Constructor to initialize values
LFUCache(int capacity) {
this.capacity = capacity;
curSize = 0;
curTime = 0;
head = new Node(-1, -1, -1);
}
// Function to get the key's value
int get(int key) {
curTime++;
Node temp = head;
while(temp.next != null) {
if(temp.next.key == key) {
temp.next.cnt++;
temp.next.timeStamp = curTime;
return temp.next.value;
}
temp = temp.next;
}
return -1;
}
// Function to put a key-value pair
void put(int key, int value) {
curTime++;
if(capacity == 0) return;
Node temp = head;
while(temp.next != null) {
if(temp.next.key == key) {
temp.next.value = value;
temp.next.cnt++;
temp.next.timeStamp = curTime;
return;
}
temp = temp.next;
}
if(curSize < capacity) {
curSize++;
temp = head;
while(temp.next != null) {
temp = temp.next;
}
temp.next = new Node(key, value, curTime);
return;
}
else {
int minCnt = Integer.MAX_VALUE;
int minTime = Integer.MAX_VALUE;
Node minNode = null;
temp = head;
while(temp.next != null) {
if(temp.next.cnt < minCnt ||
(temp.next.cnt == minCnt &&
temp.next.timeStamp < minTime)) {
minCnt = temp.next.cnt;
minTime = temp.next.timeStamp;
minNode = temp;
}
temp = temp.next;
}
minNode.next = minNode.next.next;
temp = head;
while(temp.next != null) {
temp = temp.next;
}
temp.next = new Node(key, value, curTime);
}
}
}
class GfG {
public static void main(String[] args) {
LFUCache cache = new LFUCache(2);
cache.put(1, 1);
cache.put(2, 2);
System.out.print(cache.get(1) + " ");
cache.put(3, 3);
System.out.print(cache.get(2) + " ");
cache.put(4, 4);
System.out.print(cache.get(3) + " ");
System.out.print(cache.get(4) + " ");
cache.put(5, 5);
}
}
Python
# Python Program to implement LFU Cache
# singly singly linked list
class Node:
def __init__(self, key, value, timeStamp):
self.key = key
self.value = value
self.cnt = 1
self.timeStamp = timeStamp
self.next = None
# LFU Cache class
class LFUCache:
# Constructor to initialize values
def __init__(self, capacity):
self.capacity = capacity
self.curSize = 0
self.curTime = 0
self.head = Node(-1, -1, -1)
# Function to get the key's value
def get(self, key):
self.curTime += 1
temp = self.head
while temp.next is not None:
if temp.next.key == key:
temp.next.cnt += 1
temp.next.timeStamp = self.curTime
return temp.next.value
temp = temp.next
return -1
# Function to put a key-value pair
def put(self, key, value):
self.curTime += 1
if self.capacity == 0:
return
temp = self.head
while temp.next is not None:
if temp.next.key == key:
temp.next.value = value
temp.next.cnt += 1
temp.next.timeStamp = self.curTime
return
temp = temp.next
if self.curSize < self.capacity:
self.curSize += 1
temp = self.head
while temp.next is not None:
temp = temp.next
temp.next = Node(key, value, self.curTime)
return
else:
minCnt = float('inf')
minTime = float('inf')
minNode = None
temp = self.head
while temp.next is not None:
if temp.next.cnt < minCnt or (
temp.next.cnt == minCnt and temp.next.timeStamp < minTime
):
minCnt = temp.next.cnt
minTime = temp.next.timeStamp
minNode = temp
temp = temp.next
minNode.next = minNode.next.next
temp = self.head
while temp.next is not None:
temp = temp.next
temp.next = Node(key, value, self.curTime)
if __name__ == "__main__":
cache = LFUCache(2)
cache.put(1, 1)
cache.put(2, 2)
print(cache.get(1), end=" ")
cache.put(3, 3)
print(cache.get(2), end=" ")
cache.put(4, 4)
print(cache.get(3), end=" ")
print(cache.get(4), end=" ")
cache.put(5, 5)
C#
// C# Program to implement LFU Cache
// using singly linked list
using System;
class Node {
public int key, value, timeStamp, cnt;
public Node next;
public Node(int key, int value, int timeStamp) {
this.key = key;
this.value = value;
this.cnt = 1;
this.timeStamp = timeStamp;
this.next = null;
}
}
// LFU Cache class
class LFUCache {
private int capacity;
private int curSize;
private int curTime;
Node head;
// Constructor to initialize values
public LFUCache(int capacity) {
this.capacity = capacity;
curSize = 0;
curTime = 0;
head = new Node(-1, -1, -1);
}
// Function to get the key's value
public int Get(int key) {
curTime++;
Node temp = head;
while(temp.next != null) {
if(temp.next.key == key) {
temp.next.cnt++;
temp.next.timeStamp = curTime;
return temp.next.value;
}
temp = temp.next;
}
return -1;
}
// Function to put a key-value pair
public void Put(int key, int value) {
curTime++;
if(capacity == 0) return;
Node temp = head;
while(temp.next != null) {
if(temp.next.key == key) {
temp.next.value = value;
temp.next.cnt++;
temp.next.timeStamp = curTime;
return;
}
temp = temp.next;
}
if(curSize < capacity) {
curSize++;
temp = head;
while(temp.next != null) {
temp = temp.next;
}
temp.next = new Node(key, value, curTime);
return;
}
else {
int minCnt = int.MaxValue;
int minTime = int.MaxValue;
Node minNode = null;
temp = head;
while(temp.next != null) {
if(temp.next.cnt < minCnt ||
(temp.next.cnt == minCnt &&
temp.next.timeStamp < minTime)) {
minCnt = temp.next.cnt;
minTime = temp.next.timeStamp;
minNode = temp;
}
temp = temp.next;
}
minNode.next = minNode.next.next;
temp = head;
while(temp.next != null) {
temp = temp.next;
}
temp.next = new Node(key, value, curTime);
}
}
}
class GfG {
static void Main() {
LFUCache cache = new LFUCache(2);
cache.Put(1, 1);
cache.Put(2, 2);
Console.Write(cache.Get(1) + " ");
cache.Put(3, 3);
Console.Write(cache.Get(2) + " ");
cache.Put(4, 4);
Console.Write(cache.Get(3) + " ");
Console.Write(cache.Get(4) + " ");
cache.Put(5, 5);
}
}
JavaScript
// JavaScript Program to implement LFU Cache
// using singly linked list
class Node {
constructor(key, value, timeStamp) {
this.key = key;
this.value = value;
this.cnt = 1;
this.timeStamp = timeStamp;
this.next = null;
}
}
// LFU Cache class
class LFUCache {
// Constructor to initialize values
constructor(capacity) {
this.capacity = capacity;
this.curSize = 0;
this.curTime = 0;
this.head = new Node(-1, -1, -1);
}
// Function to get the key's value
get(key) {
this.curTime++;
let temp = this.head;
while (temp.next !== null) {
if (temp.next.key === key) {
temp.next.cnt++;
temp.next.timeStamp = this.curTime;
return temp.next.value;
}
temp = temp.next;
}
return -1;
}
// Function to put a key-value pair
put(key, value) {
this.curTime++;
if (this.capacity === 0) return;
let temp = this.head;
while(temp.next != null) {
if(temp.next.key == key) {
temp.next.value = value;
temp.next.cnt++;
temp.next.timeStamp = this.curTime;
return;
}
temp = temp.next;
}
if(this.curSize < this.capacity) {
this.curSize++;
temp = this.head;
while(temp.next != null) {
temp = temp.next;
}
temp.next = new Node(key, value, this.curTime);
return;
}
else {
let minCnt = Infinity, minTime = Infinity;
let minNode = null;
temp = this.head;
while(temp.next != null) {
if(temp.next.cnt < minCnt ||
(temp.next.cnt == minCnt &&
temp.next.timeStamp < minTime)) {
minCnt = temp.next.cnt;
minTime = temp.next.timeStamp;
minNode = temp;
}
temp = temp.next;
}
minNode.next = minNode.next.next;
temp = this.head;
while(temp.next != null) {
temp = temp.next;
}
temp.next = new Node(key, value, this.curTime);
}
}
}
const cache = new LFUCache(2);
cache.put(1, 1);
cache.put(2, 2);
console.log(cache.get(1) + " ");
cache.put(3, 3);
console.log(cache.get(2) + " ");
cache.put(4, 4);
console.log(cache.get(3) + " ");
console.log(cache.get(4) + " ");
cache.put(5, 5);
Time Complexity: O(n), for each get() and put() operations where n is capacity of cache.
Auxiliary Space: O(capacity)
[Expected Approach] Using Doubly Linked List and Hashing
The idea is to create separate doubly linked list where each DLL stores the nodes with similar frequency from highest to lower priority order. To manage these, create two hash map, first to map the key with its corresponding node and other to map the frequency with head and tail of its corresponding dll. Also, maintain a counter minFreq that keep track of the minimum frequency across all nodes in this cache.
- get (key): Check if key is present in cacheMap or not. If key is not in cacheMap simply return -1, else increment the frequency freq of key by 1 and add the key's node just after the head of dll of frequency freq+1. Also, remove the node from current dll and return the key's value.
- put (key, value): If key is present in cacheMap, update the key-value pair and increment the frequency freq of key by 1. And similar to the get() operation, remove the node from current dll and add it to next dll. Else if key is absent and cache is not full, create the new node corresponding to key-value pair and add this node to dll of frequency 1. Else if cache is full, remove the node connected to the tail of dll of minFreq which is the least frequent node with least priority and add the new node to dll of frequency 1.
illustration:
C++
// C++ Program to implement LFU (Least Frequently Used) cache
#include <bits/stdc++.h>
using namespace std;
class Node {
public:
int key;
int value;
int cnt;
Node *next;
Node *prev;
Node(int key, int val) {
this->key = key;
this->value = val;
// Initial frequency is 1
cnt = 1;
}
};
// LFU Cache class
class LFUCache {
public:
// Maps a key to the corresponding Node
unordered_map<int, Node *> cacheMap;
// Maps frequency to a pair of head and tail pointers
unordered_map<int, pair<Node *, Node *>> freqMap;
// Tracks the minimum frequency of the cache
int minFreq;
// Maximum capacity of the cache
int capacity;
// Constructor to initialize LFUCache with a given capacity
LFUCache(int capacity) {
this->capacity = capacity;
// Initial minimum frequency is 0
minFreq = 0;
}
// Function to get the value associated with a key
int get(int key) {
// Return -1 if key is not found in the cache
if (cacheMap.find(key) == cacheMap.end()) {
return -1;
}
// Retrieve the Node and update its frequency
Node *node = cacheMap[key];
int res = node->value;
updateFreq(node);
return res;
}
// Function to add or update a key-value pair in the cache
void put(int key, int value) {
// Do nothing if the cache has zero capacity
if (capacity == 0)
return;
// Update value if key already exists in the cache
if (cacheMap.find(key) != cacheMap.end()) {
Node *node = cacheMap[key];
node->value = value;
updateFreq(node);
}
// Add a new key-value pair to the cache
else {
// Remove the least frequently used node if cache is full
if (cacheMap.size() == capacity) {
Node *node = freqMap[minFreq].second->prev;
cacheMap.erase(node->key);
remove(node);
// Remove the frequency list if it's empty
if (freqMap[minFreq].first->next == freqMap[minFreq].second) {
freqMap.erase(minFreq);
}
// Free memory
delete node;
}
// Create a new node for the key-value pair
Node *node = new Node(key, value);
cacheMap[key] = node;
// Reset minimum frequency to 1
minFreq = 1;
add(node, 1);
}
}
// Add a node to the frequency list
void add(Node *node, int freq) {
// Initialize the frequency list if it doesn't exist
if (freqMap.find(freq) == freqMap.end()) {
// Dummy head node
Node *head = new Node(-1, -1);
// Dummy tail node
Node *tail = new Node(-1, -1);
head->next = tail;
tail->prev = head;
freqMap[freq] = {head, tail};
}
// Insert the node right after the head
Node *head = freqMap[freq].first;
Node *temp = head->next;
node->next = temp;
node->prev = head;
head->next = node;
temp->prev = node;
}
// Remove a node from the frequency list
void remove(Node *node) {
// Update pointers to exclude the node
Node *delprev = node->prev;
Node *delnext = node->next;
delprev->next = delnext;
delnext->prev = delprev;
}
// Update the frequency of a node
void updateFreq(Node *node) {
// Get the current frequency
int oldFreq = node->cnt;
// Increment the frequency
node->cnt++;
// Remove the node from the current frequency list
remove(node);
// Remove the frequency list if it becomes empty
if (freqMap[oldFreq].first->next == freqMap[oldFreq].second) {
freqMap.erase(oldFreq);
// Update minimum frequency if needed
if (minFreq == oldFreq) {
minFreq++;
}
}
// Add the node to the updated frequency list
add(node, node->cnt);
}
};
int main() {
LFUCache cache(2);
cache.put(1, 1);
cache.put(2, 2);
cout << cache.get(1) << " ";
cache.put(3, 3);
cout << cache.get(2) << " ";
cache.put(4, 4);
cout << cache.get(3) << " ";
cout << cache.get(4) << " ";
cache.put(5, 5);
return 0;
}
Java
// Java program to implement LFU (Least Frequently Used)
// using hashing
import java.util.*;
class Node {
int key;
int value;
int cnt;
Node next;
Node prev;
Node(int key, int val) {
this.key = key;
this.value = val;
// Initial frequency is 1
cnt = 1;
}
}
class LFUCache {
// Maps key to the Node
private Map<Integer, Node> cacheMap;
// Maps frequency to doubly linked list
//(head, tail) of Nodes with that frequency
private Map<Integer, Pair<Node, Node> > freqMap;
// Tracks the minimum frequency
private int minFreq;
// Capacity of the LFU cache
private int capacity;
// Constructor to initialize LFUCache with a given capacity
LFUCache(int capacity) {
this.capacity = capacity;
// Initial minimum frequency is 0
minFreq = 0;
cacheMap = new HashMap<>();
freqMap = new HashMap<>();
}
// Function to get the value for a given key
int get(int key) {
// Return -1 if key is not found in the cache
if (!cacheMap.containsKey(key)) {
return -1;
}
// Retrieve the Node and update its frequency
Node node = cacheMap.get(key);
int res = node.value;
updateFreq(node);
return res;
}
// Function to put a key-value pair into the cache
void put(int key, int value) {
// Do nothing if the cache has zero capacity
if (capacity == 0) {
return;
}
// Update value if key already exists in the cache
if (cacheMap.containsKey(key)) {
Node node = cacheMap.get(key);
node.value = value;
updateFreq(node);
}
// Add a new key-value pair to the cache
else {
// Remove the least frequently used node if cache is full
if (cacheMap.size() == capacity) {
Node node
= freqMap.get(minFreq).second.prev;
cacheMap.remove(node.key);
remove(node);
// Remove the frequency list if it's empty
if (freqMap.get(minFreq).first.next
== freqMap.get(minFreq).second) {
freqMap.remove(minFreq);
}
}
// Create a new node for the key-value pair
Node node = new Node(key, value);
cacheMap.put(key, node);
// Reset minimum frequency to 1
minFreq = 1;
add(node, 1);
}
}
// Add a node right after the head
void add(Node node, int freq) {
// Initialize the frequency list if it doesn't exist
if (!freqMap.containsKey(freq)) {
// Dummy head node
Node head = new Node(-1, -1);
// Dummy tail node
Node tail = new Node(-1, -1);
head.next = tail;
tail.prev = head;
freqMap.put(freq, new Pair<>(head, tail));
}
// Insert the node right after the head
Node head = freqMap.get(freq).first;
Node temp = head.next;
node.next = temp;
node.prev = head;
head.next = node;
temp.prev = node;
}
// Remove a node from the list
void remove(Node node) {
// Update pointers to exclude the node
Node delprev = node.prev;
Node delnext = node.next;
delprev.next = delnext;
delnext.prev = delprev;
}
// Update the frequency of a node
void updateFreq(Node node) {
// Get the current frequency
int oldFreq = node.cnt;
// Increment the frequency
node.cnt++;
// Remove the node from the current frequency list
remove(node);
if (freqMap.get(oldFreq).first.next
== freqMap.get(oldFreq).second) {
freqMap.remove(oldFreq);
// Update minimum frequency if needed
if (minFreq == oldFreq) {
minFreq++;
}
}
// Add the node to the updated frequency list
add(node, node.cnt);
}
static class Pair<F, S> {
F first;
S second;
Pair(F first, S second) {
this.first = first;
this.second = second;
}
}
}
class GfG {
public static void main(String[] args) {
LFUCache cache = new LFUCache(2);
cache.put(1, 1);
cache.put(2, 2);
System.out.print(cache.get(1) + " ");
cache.put(3, 3);
System.out.print(cache.get(2) + " ");
cache.put(4, 4);
System.out.print(cache.get(3) + " ");
System.out.print(cache.get(4) + " ");
cache.put(5, 5);
}
}
Python
# Python program to implement LFU (Least Frequently Used)
# using hashing
class Node:
def __init__(self, key, val):
self.key = key
self.value = val
# Initial frequency is 1
self.cnt = 1
self.next = None
self.prev = None
class LFUCache:
# Constructor to initialize the LFU cache
def __init__(self, capacity):
# Maps key to Node
self.cacheMap = {}
# Maps frequency to doubly linked list
# (head, tail) of Nodes
self.freqMap = {}
# Tracks the minimum frequency
self.minFreq = 0
# Capacity of the LFU cache
self.capacity = capacity
# Function to get the value for a given key
def get(self, key):
# Return -1 if key is not found in the cache
if key not in self.cacheMap:
return -1
# Retrieve the Node and update its frequency
node = self.cacheMap[key]
res = node.value
self.updateFreq(node)
return res
# Function to put a key-value pair into the cache
def put(self, key, value):
# Do nothing if the cache has zero capacity
if self.capacity == 0:
return
# Update value if key already exists in the cache
if key in self.cacheMap:
node = self.cacheMap[key]
node.value = value
self.updateFreq(node)
# Add a new key-value pair to the cache
else:
# Remove the least frequently used node if cache is full
if len(self.cacheMap) == self.capacity:
node = self.freqMap[self.minFreq][1].prev
del self.cacheMap[node.key]
self.remove(node)
# Remove the frequency list if it's empty
if self.freqMap[self.minFreq][0].next == self.freqMap[self.minFreq][1]:
del self.freqMap[self.minFreq]
# Create a new node for the key-value pair
node = Node(key, value)
self.cacheMap[key] = node
# Reset minimum frequency to 1
self.minFreq = 1
self.add(node, 1)
# Add a node right after the head
def add(self, node, freq):
# Initialize the frequency list if it doesn't exist
if freq not in self.freqMap:
# Dummy head node
head = Node(-1, -1)
# Dummy tail node
tail = Node(-1, -1)
head.next = tail
tail.prev = head
self.freqMap[freq] = (head, tail)
# Insert the node right after the head
head = self.freqMap[freq][0]
temp = head.next
node.next = temp
node.prev = head
head.next = node
temp.prev = node
# Remove a node from the list
def remove(self, node):
# Update pointers to exclude the node
delprev = node.prev
delnext = node.next
delprev.next = delnext
delnext.prev = delprev
# Update the frequency of a node
def updateFreq(self, node):
# Get the current frequency
oldFreq = node.cnt
# Increment the frequency
node.cnt += 1
# Remove the node from the current frequency list
self.remove(node)
if self.freqMap[oldFreq][0].next == self.freqMap[oldFreq][1]:
del self.freqMap[oldFreq]
# Update minimum frequency if needed
if self.minFreq == oldFreq:
self.minFreq += 1
# Add the node to the updated frequency list
self.add(node, node.cnt)
if __name__ == "__main__":
cache = LFUCache(2)
cache.put(1, 1)
cache.put(2, 2)
print(cache.get(1), end=" ")
cache.put(3, 3)
print(cache.get(2), end=" ")
cache.put(4, 4)
print(cache.get(3), end=" ")
print(cache.get(4), end=" ")
cache.put(5, 5)
C#
// C# program to implement LFU (Least Frequently Used)
// using hashing
using System;
using System.Collections.Generic;
class Node {
public int key;
public int value;
public int cnt;
public Node next;
public Node prev;
public Node(int key, int val) {
this.key = key;
this.value = val;
// Initial frequency is 1
cnt = 1;
}
}
class LFUCache {
// Maps key to the Node
private Dictionary<int, Node> cacheMap;
// Maps frequency to doubly linked list
// (head, tail) of Nodes with that frequency
private Dictionary<int, Pair<Node, Node>> freqMap;
// Tracks the minimum frequency
private int minFreq;
// Capacity of the LFU cache
private int capacity;
// Constructor to initialize LFUCache with a given
// capacity
public LFUCache(int capacity) {
this.capacity = capacity;
// Initial minimum frequency is 0
minFreq = 0;
cacheMap = new Dictionary<int, Node>();
freqMap = new Dictionary<int, Pair<Node, Node> >();
}
// Function to get the value for a given key
public int Get(int key) {
// Return -1 if key is not found in the cache
if (!cacheMap.ContainsKey(key)) {
return -1;
}
// Retrieve the Node and update its frequency
Node node = cacheMap[key];
int res = node.value;
UpdateFreq(node);
return res;
}
// Function to put a key-value pair into the cache
public void Put(int key, int value) {
// Do nothing if the cache has zero capacity
if (capacity == 0) {
return;
}
// Update value if key already exists in the cache
if (cacheMap.ContainsKey(key)) {
Node node = cacheMap[key];
node.value = value;
UpdateFreq(node);
}
// Add a new key-value pair to the cache
else {
// Remove the least frequently used node if
// cache is full
if (cacheMap.Count == capacity) {
Node node = freqMap[minFreq].second.prev;
cacheMap.Remove(node.key);
Remove(node);
// Remove the frequency list if it's empty
if (freqMap[minFreq].first.next
== freqMap[minFreq].second) {
freqMap.Remove(minFreq);
}
}
// Create a new node for the key-value pair
Node newNode = new Node(key, value);
cacheMap[key] = newNode;
// Reset minimum frequency to 1
minFreq = 1;
Add(newNode, 1);
}
}
// Add a node right after the head
private void Add(Node node, int freq) {
// Initialize the frequency list if it doesn't exist
if (!freqMap.ContainsKey(freq)) {
// Dummy head node
Node head = new Node(-1, -1);
// Dummy tail node
Node tail = new Node(-1, -1);
head.next = tail;
tail.prev = head;
freqMap[freq]
= new Pair<Node, Node>(head, tail);
}
// Insert the node right after the head
Node headNode = freqMap[freq].first;
Node temp = headNode.next;
node.next = temp;
node.prev = headNode;
headNode.next = node;
temp.prev = node;
}
// Remove a node from the list
private void Remove(Node node) {
// Update pointers to exclude the node
Node delprev = node.prev;
Node delnext = node.next;
delprev.next = delnext;
delnext.prev = delprev;
}
// Update the frequency of a node
private void UpdateFreq(Node node) {
// Get the current frequency
int oldFreq = node.cnt;
// Increment the frequency
node.cnt++;
// Remove the node from the current frequency list
Remove(node);
if (freqMap[oldFreq].first.next
== freqMap[oldFreq].second) {
freqMap.Remove(oldFreq);
// Update minimum frequency if needed
if (minFreq == oldFreq) {
minFreq++;
}
}
// Add the node to the updated frequency list
Add(node, node.cnt);
}
// Helper class to represent a pair of nodes
private class Pair<F, S> {
public F first;
public S second;
public Pair(F first, S second) {
this.first = first;
this.second = second;
}
}
}
class GfG {
static void Main(string[] args) {
LFUCache cache = new LFUCache(2);
cache.Put(1, 1);
cache.Put(2, 2);
Console.Write(cache.Get(1) + " ");
cache.Put(3, 3);
Console.Write(cache.Get(2) + " ");
cache.Put(4, 4);
Console.Write(cache.Get(3) + " ");
Console.Write(cache.Get(4) + " ");
cache.Put(5, 5);
}
}
JavaScript
// JavaScript program to implement LFU (Least Frequently
// Used) using hashing
class Node {
constructor(key, val) {
this.key = key;
this.value = val;
// Initial frequency is 1
this.cnt = 1;
this.next = null;
this.prev = null;
}
}
class LFUCache {
// Constructor to initialize LFUCache with a given
// capacity
constructor(capacity) {
// Maps key to the Node
this.cacheMap = new Map();
// Maps frequency to doubly linked list
// (head, tail) of Nodes with that frequency
this.freqMap = new Map();
// Tracks the minimum frequency
this.minFreq = 0;
// Capacity of the LFU cache
this.capacity = capacity;
}
get(key) {
// Return -1 if key is not found in the cache
if (!this.cacheMap.has(key)) {
return -1;
}
// Retrieve the Node and update its frequency
const node = this.cacheMap.get(key);
const res = node.value;
this.updateFreq(node);
return res;
}
// Function to put a key-value pair into the cache
put(key, value) {
// Do nothing if the cache has zero capacity
if (this.capacity === 0)
return;
// Update value if key already exists in the cache
if (this.cacheMap.has(key)) {
const node = this.cacheMap.get(key);
node.value = value;
this.updateFreq(node);
}
// Add a new key-value pair to the cache
else {
// Remove the least frequently used node if
// cache is full
if (this.cacheMap.size === this.capacity) {
const node = this.freqMap.get(this.minFreq)
.tail.prev;
this.cacheMap.delete(node.key);
this.remove(node);
// Remove the frequency list if it's empty
if (this.freqMap.get(this.minFreq).head.next
=== this.freqMap.get(this.minFreq)
.tail) {
this.freqMap.delete(this.minFreq);
}
}
// Create a new node for the key-value pair
const node = new Node(key, value);
this.cacheMap.set(key, node);
// Reset minimum frequency to 1
this.minFreq = 1;
this.add(node, 1);
}
}
// Add a node right after the head
add(node, freq) {
// Initialize the frequency list if it doesn't exist
if (!this.freqMap.has(freq)) {
// Dummy head node
const head = new Node(-1, -1);
// Dummy tail node
const tail = new Node(-1, -1);
head.next = tail;
tail.prev = head;
this.freqMap.set(freq, {head, tail});
}
// Insert the node right after the head
const head = this.freqMap.get(freq).head;
const temp = head.next;
node.next = temp;
node.prev = head;
head.next = node;
temp.prev = node;
}
// Remove a node from the list
remove(node) {
// Update pointers to exclude the node
const delprev = node.prev;
const delnext = node.next;
delprev.next = delnext;
delnext.prev = delprev;
}
// Update the frequency of a node
updateFreq(node) {
// Get the current frequency
const oldFreq = node.cnt;
// Increment the frequency
node.cnt++;
// Remove the node from the current frequency list
this.remove(node);
if (this.freqMap.get(oldFreq).head.next
=== this.freqMap.get(oldFreq).tail) {
this.freqMap.delete(oldFreq);
// Update minimum frequency if needed
if (this.minFreq === oldFreq) {
this.minFreq++;
}
}
// Add the node to the updated frequency list
this.add(node, node.cnt);
}
}
const cache = new LFUCache(2);
cache.put(1, 1);
cache.put(2, 2);
console.log(cache.get(1) + " ");
cache.put(3, 3);
console.log(cache.get(2) + " ");
cache.put(4, 4);
console.log(cache.get(3) + " ");
console.log(cache.get(4) + " ");
cache.put(5, 5);
Time Complexity : get(key) - O(1) and put(key, value) - O(1)
Auxiliary Space : O(capacity)
Similar Reads
Page Faults in LFU | Implementation
In this, it is using the concept of paging for memory management, a page replacement algorithm is needed to decide which page needs to be replaced when the new page comes in. Whenever a new page is referred to and is not present in memory, the page fault occurs and the Operating System replaces one
12 min read
Most Frequently Used (MFU) Algorithm in Operating System
MFU Algorithm is a Page Replacement Algorithm in the Operating System that replaces the page accessed a maximum number of times in the past. If more than one page is accessed the same number of times, then the page which occupied the frame first will be replaced. Page ReplacementIt is a technique of
5 min read
Directory Implementation in Operating System
Directory implementation in the operating system can be done using Singly Linked List and Hash table. The efficiency, reliability, and performance of a file system are greatly affected by the selection of directory-allocation and directory-management algorithms. There are numerous ways in which the
5 min read
Introduction and Array Implementation of Deque
A Deque (Double-Ended Queue) is a data structure that allows elements to be added or removed from both endsâfront and rear. Unlike a regular queue, which only allows insertions at the rear and deletions from the front, a deque provides flexibility by enabling both operations at either end. This make
3 min read
Implementation of Hash Table in C/C++ using Separate Chaining
Introduction: Hashing is a technique that maps a large set of data to a small set of data. It uses a hash function for doing this mapping. It is an irreversible process and we cannot find the original value of the key from its hashed value because we are trying to map a large set of data into a smal
10 min read
Name some Queue implementations and compare them by efficiency of operations
A queue is a linear data structure in which insertion is done from one end called the rear end and deletion is done from another end called the front end. It follows FIFO (First In First Out) approach. The insertion in the queue is called enqueue operation and deletion in the queue is called dequeue
10 min read
How to Implement Forward DNS Look Up Cache?
We have discussed implementation of Reverse DNS Look Up Cache. Forward DNS look up is getting IP address for a given domain name typed in the web browser. The cache should do the following operations : 1. Add a mapping from URL to IP address 2. Find IP address for a given URL. There are a few change
13 min read
Implementation of Contiguous Memory Management Techniques
Memory Management Techniques are basic techniques that are used in managing the memory in the operating system. They are classified broadly into two categories:ContiguousNon-contiguous Contiguous memory allocation is a memory allocation strategy. As the name implies, we utilize this technique to ass
3 min read
LIFO (Last-In-First-Out) approach in Programming
Prerequisites - FIFO (First-In-First-Out) approach in Programming, FIFO vs LIFO approach in Programming LIFO is an abbreviation for last in, first out. It is a method for handling data structures where the first element is processed last and the last element is processed first. Real-life example: In
6 min read
Stable-Storage Implementation in Operating system
By definition, information residing in the Stable-Storage is never lost. Even, if the disk and CPU have some errors, it will never lose any data. Stable-Storage Implementation: To achieve such storage, we need to replicate the required information on multiple storage devices with independent failure
3 min read