Mark As Completed Discussion

Implement an LFU Cache Using Doubly Linked List

To avoid iterating through the whole list, remove the element directly, and insert it against other count value sortedCountMap, we'll be needing a Doubly Linked List. Note that this will now take O(1) time.

1let sortedCountMap = new Map();

Using Doubly Linked List

Here, we'll store Node object as the value in valueMap. It will have key, value, and the position of the node in the linked list stored as the value in sortedCountMap:

1class Node {
2    constructor(key, value) {
3        super();
4        this.key = key;
5        this.value = value;
6    }
7}

Let's have a look at the DoubleLinkedList class:

1class DoubleLinkedList {
2
3  constructor(){
4    this.n = 0;
5    this.head = null;
6    this.tail = null;
7  }
8
9  add(node) {
10
11    if (this.head == null) {
12
13      this.head = node;
14    }
15
16    else {
17
18      this.tail.next = node;
19      node.previous = this.tail;
20
21    }
22    this.tail = node;
23    this.n++;
24
25  }
26
27  remove(node) {
28
29    if (node.next == null) {
30      this.tail = node.previous;
31
32    } else {
33
34      node.next.previous = node.previous;
35
36    }
37
38    if (node.previous == null) {
39
40      this.head = node.next;
41    } else {
42
43      node.previous.next = node.next;
44
45    }
46
47    this.n--;
48
49  }
50
51  size() {
52
53    return this.n;
54
55  }
56
57  getHead() {
58    return this.head;
59  }
60
61}

To get an element, and update the value in sortedCountMap takes O(1) time now:

1get(key) {
2  if (!this.valueMap.has(key) || this.size === 0) {
3    return -1;
4  }
5  let nodeToRemove = this.valueMap.get(key);
6  let node = new Node(key, nodeToRemove.value);
7  let count = this.countMap.get(key);
8  this.sortedCountMap.get(count).remove(nodeToRemove);
9  this.removeEntryFromSortedCountMapIfListIsEmpty(count);
10  this.valueMap.delete(key);
11  this.countMap.delete(key);
12  this.valueMap.set(key, node);
13  this.countMap.set(key, count + 1);
14  this.sortedCountMap.set(count + 1, this.sortedCountMap.get(count + 1) || new DoubleLinkedList());
15  this.sortedCountMap.get(count + 1).add(node);
16  return this.valueMap.get(key).value;
17}

Finally, let's have a look at how a new element can be inserted, or existing can be updated:

1put(key, value) {
2    if (valueMap.has(key) == false && size > 0) {
3
4        var node = new Node(key, value);
5        if (valueMap.size() == size) {
6            var lowestFrequency = Math.min(...sortedCountMap.keys());
7            var nodeToRemove = sortedCountMap.get(lowestFrequency).getHead();
8                sortedCountMap.get(lowestFrequency).remove(nodeToRemove);
9                removeEntryFromSortedCountMapIfListIsEmpty(lowestFrequency);
10
11            var keyToRemove = nodeToRemove.getKey();
12            valueMap.delete(keyToRemove);
13            countMap.delete(keyToRemove);
14        }
15        sortedCountMap.set(1, (sortedCountMap.get(1) || new DoubleLinkedList())).add(node);
16        valueMap.set(key, node);
17        countMap.set(key, 1);
18
19    } else if (valueMap.has(key) && size > 0) {
20        var node = new Node(key, value);
21        var nodeToRemove = valueMap.get(key);
22        var count = countMap.get(key);
23        sortedCountMap.get(count).remove(nodeToRemove);
24        removeEntryFromSortedCountMapIfListIsEmpty(count);
25        valueMap.delete(key);
26        countMap.delete(key);
27        valueMap.set(key, node);
28        countMap.set(key, count + 1);
29        sortedCountMap.set(count + 1, (sortedCountMap.get(count + 1) || new DoubleLinkedList())).add(node);
30
31    }
32}