Algorithm
Problem Name: 460. LFU Cache
Design and implement a data structure for a Least Frequently Used (LFU) cache.
Implement the LFUCache
class:
LFUCache(int capacity)
Initializes the object with thecapacity
of the data structure.int get(int key)
Gets the value of thekey
if thekey
exists in the cache. Otherwise, returns-1
.void put(int key, int value)
Update the value of thekey
if present, or inserts thekey
if not already present. When the cache reaches itscapacity
, it should invalidate and remove the least frequently used key before inserting a new item. For this problem, when there is a tie (i.e., two or more keys with the same frequency), the least recently usedkey
would be invalidated.
To determine the least frequently used key, a use counter is maintained for each key in the cache. The key with the smallest use counter is the least frequently used key.
When a key is first inserted into the cache, its use counter is set to 1
(due to the put
operation). The use counter for a key in the cache is incremented either a get
or put
operation is called on it.
The functions get
and put
must each run in O(1)
average time complexity.
Example 1:
Input ["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"] [[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]] Output [null, null, null, 1, null, -1, 3, null, -1, 3, 4] Explanation // cnt(x) = the use counter for key x // cache=[] will show the last used order for tiebreakers (leftmost element is most recent) LFUCache lfu = new LFUCache(2); lfu.put(1, 1); // cache=[1,_], cnt(1)=1 lfu.put(2, 2); // cache=[2,1], cnt(2)=1, cnt(1)=1 lfu.get(1); // return 1 // cache=[1,2], cnt(2)=1, cnt(1)=2 lfu.put(3, 3); // 2 is the LFU key because cnt(2)=1 is the smallest, invalidate 2. // cache=[3,1], cnt(3)=1, cnt(1)=2 lfu.get(2); // return -1 (not found) lfu.get(3); // return 3 // cache=[3,1], cnt(3)=2, cnt(1)=2 lfu.put(4, 4); // Both 1 and 3 have the same cnt, but 1 is LRU, invalidate 1. // cache=[4,3], cnt(4)=1, cnt(3)=2 lfu.get(1); // return -1 (not found) lfu.get(3); // return 3 // cache=[3,4], cnt(4)=1, cnt(3)=3 lfu.get(4); // return 4 // cache=[4,3], cnt(4)=2, cnt(3)=3
Constraints:
0 <= capacity <= 104
0 <= key <= 105
0 <= value <= 109
- At most
2 * 105
calls will be made toget
andput
.
Code Examples
#1 Code Example with C Programming
Code -
C Programming
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.get(3); // returns 3.
cache.put(4, 4); // evicts key 1.
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
*/
typedef struct item_s {
int key;
int val;
int ref;
// add mutex if it is used in multithreaded program
struct item_s *shadow;
struct item_s *prev;
struct item_s *next;
} item_t;
typedef struct {
int sz;
item_t **data;
item_t *head;
item_t *buff;
} LFUCache;
LFUCache* lFUCacheCreate(int capacity) {
LFUCache *p;
item_t *buff;
int i;
if (capacity < = 0) return NULL;
p = malloc(sizeof(item_t));
//assert(p);
p->data = calloc(capacity, sizeof(item_t *));
//assert(p->data);
buff = calloc(capacity, sizeof(item_t));
//assert(buff);
p->head = p->buff = &buff[0];
if (capacity > 1) {
buff[0].next = &buff[1];
for (i = 1; i < capacity - 1; i ++) {
buff[i].prev = &buff[i - 1];
buff[i].next = &buff[i + 1];
}
buff[capacity - 1].prev = &buff[capacity - 2];
}
p->sz = capacity;
return p;
}
void move2next(item_t *t, LFUCache *obj) {
item_t *a, *b;
a = t;
b = t->next;
while (b && b->ref < = t->ref) {
a = b;
b = b->next;
}
if (a != t) {
t->next->prev = t->prev;
if (t->prev) t->prev->next = t->next;
else obj->head = t->next;
a->next = t;
t->prev = a;
t->next = b;
if (b) b->prev = t;
}
}
item_t *lookup(LFUCache* obj, int key) {
item_t *t;
t = obj->data[key % obj->sz];
while (t && t->key != key) {
t = t->shadow;
}
return t;
}
int lFUCacheGet(LFUCache* obj, int key) {
item_t *t, *n, *np;
t = obj ? lookup(obj, key) : NULL;
if (!t) {
return -1;
}
t->ref ++;
move2next(t, obj);
return t->val;
}
void lFUCachePut(LFUCache* obj, int key, int value) {
item_t *t, **pp;
if (!obj) return;
t = lookup(obj, key);
if (!t) {
t = obj->head; // least frequently used
// take it out of the cache
pp = &obj->data[t->key % obj->sz];
while (*pp && *pp != t) {
pp = &(*pp)->shadow;
}
if (*pp) {
*pp = (*pp)->shadow;
}
// put it back with new key
t->key = key;
t->ref = 1;
t->shadow = obj->data[key % obj->sz];
obj->data[key % obj->sz] = t;
} else {
t->ref ++;
}
t->val = value;
move2next(t, obj);
}
void lFUCacheFree(LFUCache* obj) {
if (!obj) return;
free(obj->buff);
free(obj->data);
free(obj);
}
Copy The Code &
Try With Live Editor
Input
Output
#2 Code Example with Java Programming
Code -
Java Programming
class LFUCache {
private final Map keyToNodeMap;
private final Map < Integer, Node[]> frequencyToNodeMap;
private final Map keyToFrequencyMap;
private int capacity;
private int currentCapacity;
public LFUCache(int capacity) {
this.capacity = capacity;
this.currentCapacity = 0;
this.keyToNodeMap = new HashMap < >();
this.frequencyToNodeMap = new TreeMap<>();
this.keyToFrequencyMap = new HashMap < >();
}
public int get(int key) {
if (!keyToNodeMap.containsKey(key)) {
return -1;
}
Node node = keyToNodeMap.get(key);
removeNode(node);
int currentFrequency = keyToFrequencyMap.get(key);
int newFrequency = currentFrequency + 1;
keyToFrequencyMap.put(key, newFrequency);
addNodeToFrequencyHead(node, newFrequency);
return node.val;
}
public void put(int key, int value) {
if (this.capacity == 0) {
return;
}
removeNodeIfCapacityReached(key);
Node node = getNode(key, value);
int newFrequency = keyToFrequencyMap.getOrDefault(key, 0) + 1;
keyToFrequencyMap.put(key, newFrequency);
keyToNodeMap.put(key, node);
if (newFrequency > 1) {
removeNode(node);
}
addNodeToFrequencyHead(node, newFrequency);
}
private void removeNodeIfCapacityReached(int key) {
if (!keyToNodeMap.containsKey(key) && this.currentCapacity == capacity) {
for (Integer freq : frequencyToNodeMap.keySet()) {
Node[] nodes = frequencyToNodeMap.get(freq);
if (nodes[1].prev.val == -1) {
continue;
}
Node toRemove = nodes[1].prev;
removeNode(toRemove);
keyToNodeMap.remove(toRemove.key);
keyToFrequencyMap.remove(toRemove.key);
this.currentCapacity--;
break;
}
}
}
private Node getNode(int key, int value) {
Node node;
if (keyToNodeMap.containsKey(key)) {
node = keyToNodeMap.get(key);
removeNode(node);
node.val = value;
} else {
this.currentCapacity++;
node = new Node(value, key);
}
return node;
}
private void addNodeToFrequencyHead(Node node, int newFrequency) {
if (!frequencyToNodeMap.containsKey(newFrequency)) {
Node head = new Node(-1, Integer.MIN_VALUE);
Node tail = new Node(-1, Integer.MAX_VALUE);
head.next = tail;
tail.prev = head;
frequencyToNodeMap.put(newFrequency, new Node[]{head, tail});
}
Node headNode = frequencyToNodeMap.get(newFrequency)[0];
Node nextToHead = headNode.next;
nextToHead.prev = node;
node.next = nextToHead;
headNode.next = node;
node.prev = headNode;
}
private void removeNode(Node node) {
Node prevNode = node.prev;
Node nextNode = node.next;
prevNode.next = nextNode;
nextNode.prev = prevNode;
}
private static class Node {
int val;
int key;
Node next;
Node prev;
public Node(int val, int key) {
this.val = val;
this.key = key;
}
}
}
Copy The Code &
Try With Live Editor
Input
Output
#3 Code Example with Javascript Programming
Code -
Javascript Programming
const LFUCache = function(capacity) {
this.min = -1;
this.capacity = capacity;
this.keyToVal = {};
this.keyToCount = {};
this.countToLRUKeys = {};
};
LFUCache.prototype.get = function(key) {
if (!this.keyToVal.hasOwnProperty(key)) return -1;
let count = this.keyToCount[key];
let idx = this.countToLRUKeys[count].indexOf(key);
if (idx !== -1) this.countToLRUKeys[count].splice(idx, 1);
if (count === this.min && this.countToLRUKeys[count].length === 0) this.min++;
putCount(key, count + 1, this.keyToCount, this.countToLRUKeys);
return this.keyToVal[key];
};
LFUCache.prototype.put = function(key, value) {
if (this.capacity < = 0) return;
if (this.keyToVal.hasOwnProperty(key)) {
this.keyToVal[key] = value; // update key's value
this.get(key); // update key's count
return;
}
if (Object.keys(this.keyToVal).length >= this.capacity) {
evict(
this.countToLRUKeys[this.min][0],
this.min,
this.keyToVal,
this.countToLRUKeys
); // evict LRU from this min count bucket
}
this.min = 1;
putCount(key, this.min, this.keyToCount, this.countToLRUKeys); // adding new key and count
this.keyToVal[key] = value; // adding new key and value
};
function evict(key, min, keyToVal, countToLRUKeys) {
let idx = countToLRUKeys[min].indexOf(key);
if (idx !== -1) countToLRUKeys[min].splice(idx, 1);
delete keyToVal[key];
}
function putCount(key, count, keyToCount, countToLRUKeys) {
keyToCount[key] = count;
if (countToLRUKeys.hasOwnProperty(count)) {
countToLRUKeys[count].push(key);
} else {
countToLRUKeys[count] = [key];
}
}
Copy The Code &
Try With Live Editor
Input
Output
#4 Code Example with Python Programming
Code -
Python Programming
class Node:
def __init__(self, k, v, f):
self.key = k
self.val = v
self.freq = f
self.next = self.pre = None
class LFUCache:
def __init__(self, capacity):
self.max = capacity
self.cache = 0
self.freqLast = {}
self.Nodes = {}
self.head = self.tail = Node("#", "#", "#")
self.head.next = self.tail
self.tail.pre = self.head
def changeFreq(self, key):
node, f = self.Nodes[key], self.Nodes[key].freq
if self.freqLast[f] == node:
if node.pre.freq == f:
self.freqLast[f] = node.pre
else:
self.freqLast.pop(f)
if f + 1 in self.freqLast:
node.pre.next = node.next
node.next.pre = node.pre
node.pre = self.freqLast[f + 1]
node.next = node.pre.next
elif f in self.freqLast:
node.pre.next = node.next
node.next.pre = node.pre
node.pre = self.freqLast[f]
node.next = node.pre.next
node.pre.next = node.next.pre = node
self.freqLast[f + 1] = node
node.freq += 1
def removeFirst(self):
node, f = self.head.next, self.head.next.freq
node.pre.next = node.next
node.next.pre = node.pre
self.Nodes.pop(node.key)
if self.freqLast[f] == node:
self.freqLast.pop(f)
self.cache -= 1
def get(self, key):
if key in self.Nodes:
self.changeFreq(key)
return self.Nodes[key].val
return -1
def put(self, key, value):
if key in self.Nodes:
self.changeFreq(key)
self.Nodes[key].val = value
elif self.max:
if self.cache == self.max:
self.removeFirst()
self.cache += 1
new = Node(key, value, 1)
if 1 in self.freqLast:
new.pre = self.freqLast[1]
else:
new.pre = self.head
new.next = new.pre.next
new.pre.next = new.next.pre = new
self.freqLast[1] = self.Nodes[key] = new
Copy The Code &
Try With Live Editor
Input
Output
#5 Code Example with C# Programming
Code -
C# Programming
using System.Collections.Generic;
using System.Linq;
namespace LeetCode
{
public class _0460_LFUCache
{
private readonly SortedDictionary < int, LinkedList<int[]>> frequencyList;
private readonly Dictionary < int, LinkedListNode<int[]>> map;
private readonly int capacity;
public _0460_LFUCache(int capacity)
{
this.capacity = capacity;
frequencyList = new SortedDictionary < int, LinkedList<int[]>>();
map = new Dictionary < int, LinkedListNode<int[]>>();
}
public int Get(int key)
{
if (!map.ContainsKey(key))
return -1;
else
{
Reorder(map[key]);
}
return map[key].Value[1];
}
public void Put(int key, int value)
{
if (capacity == 0) return;
if (map.ContainsKey(key))
map[key].Value[1] = value;
else
{
if (map.Count >= capacity)
{
int min = frequencyList.Keys.First();
map.Remove(frequencyList[min].Last.Value[0]);
frequencyList[min].RemoveLast();
if (frequencyList[min].Count == 0)
frequencyList.Remove(min);
}
map.Add(key, new LinkedListNode < int[]>(new int[] { key, value, -1 }));
}
Reorder(map[key]);
}
private void Reorder(LinkedListNode < int[]> node)
{
var freq = node.Value[2];
if (frequencyList.ContainsKey(freq))
{
frequencyList[freq].Remove(node);
if (frequencyList[freq].Count == 0)
frequencyList.Remove(freq);
}
freq++;
node.Value[2]++;
if (!frequencyList.ContainsKey(freq))
frequencyList.Add(freq, new LinkedList < int[]>());
frequencyList[freq].AddFirst(node);
}
}
}
Copy The Code &
Try With Live Editor
Input
Output