LFU Cache

Asked atBloomBergMeta
1Give yourself 5 minutes
2Answer out loud, not in your head
3Then compare with the answer below

The problem

Design and implement a data structure for a **Least Frequently Used (LFU) **cache.

Implement the LFUCache class with the following functions:

LFUCache(int capacity): Initialize the object with the specified capacity. int get(int key): Retrieve the value of the key if it exists in the cache; otherwise, return -1. void put(int key, int value): Update the value of the key if it is present in the cache, or insert the key if it is not already present. If the cache has reached its capacity, invalidate and remove the least frequently used key before inserting a new item. In case of a tie (i.e., two or more keys with the same frequency), invalidate the least recently used key.

A use counter is maintained for each key in the cache to determine the least frequently used key. The key with the smallest use counter is considered the least frequently used.

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 whenever a get or put operation is called on it.

Ensure that the functions get and put run in O(1) average time complexity.

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

Input: ["LFUCache", "put", "put", "put", "put", "put", "get", "get", "get", "get", "get"] [[3], [5, 7], [4, 6], [3, 5], [2, 4], [1, 3], [1], [2], [3], [4], [5]] Output: [null, null, null, null, null, null, 3, 4, 5, -1, -1] 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(3); lfu.put(5, 7); // cache=[5], cnt(5)=1 lfu.put(4, 6); // cache=[4,5], cnt(4)=1, cnt(5)=1 lfu.put(3, 5); // cache=[3,4,5], cnt(3)=1, cnt(4)=1, cnt(5)=1 lfu.put(2, 4); // 5 is the LFU key because cnt(5)=1 is the smallest, invalidate 5. // cache=[2,3,4], cnt(2)=1, cnt(3)=1, cnt(4)=1 lfu.put(1, 3); // 4 is the LFU key because cnt(4)=1 is the smallest, invalidate 4. // cache=[1,2,3], cnt(1)=1, cnt(2)=1, cnt(3)=1 lfu.get(1); // return 3 // cache=[1,2,3], cnt(1)=2, cnt(2)=1, cnt(3)=1 lfu.get(2); // return 4 // cache=[2,1,3], cnt(1)=2, cnt(2)=2, cnt(3)=1 lfu.get(3); // return 5 // cache=[3,2,1], cnt(1)=2, cnt(2)=2, cnt(3)=2 lfu.get(4); // return -1 (not found) lfu.get(5); // return -1 (not found)

Input: ["LFUCache", "put", "get", "put", "get", "get"] [[1], [1, 10], [1], [2, 20], [1], [2]]

  • 1 <= capacity <= 103
  • 0 <= key <= 104
  • 0 <= value <= 105
  • At most 105 calls will be made to get and put.

cpp

class LFUCache {
 public:
  LFUCache(int capacity)  {
}

  int get(int key) {

  }

  void put(int key, int value) {
  
  }

};

java

class LFUCache {
    public LFUCache(int capacity) {
    }
    
    public int get(int key) {
      
    }
    
    public void put(int key, int value) {
    }
}

python

class LFUCache:
    def __init__(self, capacity):
        pass
    
    def get(self, key):
        return -1  # Placeholder
    
    def put(self, key, value):
        pass

javascript

class LFUCache {
    constructor(capacity) {
    }
    
    get(key) {
   
    }
    
    put(key, value) {
    }
}

csharp

class LFUCache {
    public LFUCache(int capacity) {

    }

    public int Get(int key) {

    }

    public void Put(int key, int value) {

    }
}

go

// LFUCache struct
type LFUCache struct {

}
// Constructor
func NewLFUCache(capacity int) *LFUCache {

}

func (lfu *LFUCache) Get(key int) int {

}

func (lfu *LFUCache) Put(key int, value int) {

}
Solve on LeetCode →
Stuck? Show a way to structure it+
  1. 01Restate the objective and identify the key invariant
  2. 02Develop the hash map plus frequency buckets approach step by step
  3. 03Walk through a small adversarial example
  4. 04Cover boundary conditions before coding
  5. 05State time and space Big-O and explain the trade-off

Reference answer

Then expect these follow-ups

  • How would the approach change for streaming input?

    Tests: constraint adaptation

  • Which boundary case is most likely to break an implementation?

    Tests: implementation extension

Free to read · better with Enzo

Practice this out loud with Enzo

Enzo runs it as a mock interview, pushes back with follow-ups, and grades you on the rubric.

Next question