712324. LFU cache

Hard

LFU Cache

Slug: lfu-cache-1665050355 Difficulty: Hard Id: 712324 Topic Tags: Hash, doubly-linked-list, Data Structures, Design-Pattern Company Tags: None

Summary

The LFU (Least Frequently Used) cache problem involves implementing a data structure that efficiently manages a cache with a limited size. The cache should store keys and their corresponding values, where the most frequently used items are stored in the front of the cache. When a key is accessed or evicted from the cache, it should be updated accordingly.

Detailed Explanation

To solve this problem, we can use a combination of a doubly-linked list and a hash map to efficiently manage the LFU cache. The hash map will store the keys and their corresponding nodes in the doubly-linked list. Each node in the list will represent an item in the cache with its frequency count.

Here's a step-by-step breakdown of the solution:

  1. Initialize an empty doubly-linked list cache and a hash map freqMap.
  2. When a key is accessed, check if it exists in the cache. If not, add it to the end of the cache and set its frequency to 1.
  3. If the key already exists in the cache, increment its frequency count.
  4. If the cache size exceeds the maximum allowed size, remove the least frequently used item (the one at the front of the list) and update the hash map accordingly.
  5. When a key is evicted from the cache, decrement its frequency count.

The time complexity for accessing or evicting an item from the cache is O(1), since we can perform these operations in constant time using the hash map and doubly-linked list. The space complexity is also O(n), where n is the number of items in the cache.

Here's an ASCII art diagram to illustrate the data structure:

      +---------------+
      |  Node 1   |
      +---------------+
           |
           |
           v
      +---------------+
      |  Node 2   |
      +---------------+
           |
           |
           v
      +---------------+
      |  ...     |
      +---------------+
           |
           |
           v
      +---------------+
      |  Node N   |
      +---------------+

The doubly-linked list is used to efficiently manage the order of items in the cache, while the hash map is used to quickly look up and update the frequency counts.

Optimized Solutions

Java

View on GeeksforGeeks
import java.util.*;

class LFUCache {
    private int capacity;
    private Map<Integer, Integer> keyFrequency;
    private Map<Integer, Node> nodeMap;
    private DoublyLinkedList cache;

    public LFUCache(int capacity) {
        this.capacity = capacity;
        this.keyFrequency = new HashMap<>();
        this.nodeMap = new HashMap<>();
        this.cache = new DoublyLinkedList();
    }

    public int get(int key) {
        if (!nodeMap.containsKey(key)) return -1;
        Node node = nodeMap.get(key);
        int frequency = keyFrequency.get(key);
        cache.moveToFront(node);

        // Update the frequency count
        keyFrequency.put(key, frequency + 1);

        return node.val;
    }

    public void put(int key, int value) {
        if (capacity <= 0) return;

        Node node = nodeMap.get(key);
        if (node != null) {
            node.val = value;
            return;
        }

        node = new Node(value);
        nodeMap.put(key, node);

        // Add the node to the cache
        cache.add(node);

        keyFrequency.put(key, 1);

        // If the cache size exceeds capacity, evict the least frequently used item
        if (cache.size() > capacity) {
            int minFreq = Integer.MAX_VALUE;
            Node minNode = null;

            for (Node n : cache) {
                int freq = keyFrequency.get(n.key);
                if (freq < minFreq) {
                    minFreq = freq;
                    minNode = n;
                }
            }

            cache.remove(minNode);
            nodeMap.remove(minNode.key);

            // Update the frequency counts
            for (Map.Entry<Integer, Integer> entry : keyFrequency.entrySet()) {
                if (entry.getValue() == minFreq) {
                    entry.setValue(entry.getValue() + 1);
                }
            }
        }
    }

    private class Node {
        int val;
        Node prev;
        Node next;

        public Node(int val) {
            this.val = val;
        }
    }

    private class DoublyLinkedList {
        private Node head;
        private Node tail;

        public void add(Node node) {
            if (head == null) {
                head = node;
                tail = node;
            } else {
                node.prev = tail;
                tail.next = node;
                tail = node;
            }
        }

        public void moveToFront(Node node) {
            remove(node);
            add(node);
        }

        public void remove(Node node) {
            if (node.prev != null) {
                node.prev.next = node.next;
            } else {
                head = node.next;
            }

            if (node.next != null) {
                node.next.prev = node.prev;
            } else {
                tail = node.prev;
            }
        }

        public int size() {
            return nodeCount(head, tail);
        }

        private int nodeCount(Node start, Node end) {
            int count = 0;

            for (Node node = start; node != end; node = node.next) {
                count++;
            }

            return count;
        }
    }
}

DevExCode

Master coding interviews with Leetcode, system design, TechBit, QuickLearn, DevTips, Tech Battles, and career services.

© 2026 DevExCode. All rights reserved.