Implement an LFU cache
Company: Salesforce
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: hard
Interview Round: Onsite
Quick Answer: This question evaluates understanding of data structures and cache eviction policies, specifically frequency tracking for LFU caches and LRU tie-breaking among equal-frequency items.
Constraints
- 0 <= capacity
- 0 <= key, value (keys/values are non-negative integers in the tests)
- Total number of get/put operations fits comfortably in memory (designed for O(1)/op)
- If capacity == 0: put is a no-op and get always returns -1
Examples
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]])
Expected Output: [None, None, None, 1, None, -1, 3, None, -1, 3, 4]
Explanation: Canonical LeetCode trace, capacity 2. get(1) makes freq(1)=2, so put(3,3) evicts key 2 (freq 1). Later put(4,4) must evict among the lowest frequency: keys 1 and 3 both have freq 1 after the previous step's gets, key 1 is least recently used, so key 1 is evicted -> get(1)=-1, get(3)=3, get(4)=4.
Input: (["LFUCache", "put", "get"], [[0], [0, 0], [0]])
Expected Output: [None, None, -1]
Explanation: Capacity 0 edge case: put does nothing and get always returns -1.
Input: (["LFUCache", "put", "put", "get", "get"], [[2], [1, 10], [2, 20], [1], [2]])
Expected Output: [None, None, None, 10, 20]
Explanation: Both keys fit in capacity 2, so both gets return their stored values; no eviction occurs.
Input: (["LFUCache", "put", "put", "put", "get", "get"], [[2], [1, 1], [2, 2], [3, 3], [1], [3]])
Expected Output: [None, None, None, None, -1, 3]
Explanation: Keys 1 and 2 both have freq 1; inserting key 3 evicts the least-recently-used among them (key 1). So get(1)=-1, get(3)=3.
Input: (["LFUCache", "put", "put", "get"], [[1], [1, 1], [2, 2], [1]])
Expected Output: [None, None, None, -1]
Explanation: Capacity 1: putting key 2 evicts key 1 (the only resident), so get(1) returns -1.
Input: (["LFUCache", "put", "put", "put", "get", "get", "get"], [[3], [1, 1], [2, 2], [1, 100], [1], [2], [1]])
Expected Output: [None, None, None, None, 100, 2, 100]
Explanation: put(1,100) updates key 1's value and bumps its frequency. With capacity 3 nothing is evicted; get(1) returns the updated 100, get(2) returns 2.
Hints
- Maintain three maps: key -> value, key -> frequency, and frequency -> an ordered collection of keys at that frequency (insertion order = recency).
- Track min_freq so eviction is O(1): evict the least-recently-used key in the freq_to_keys[min_freq] bucket.
- On a successful get (or an update via put), move the key from its current frequency bucket to the next; if you emptied the min_freq bucket, increment min_freq.
- When inserting a brand-new key, its frequency is 1, so set min_freq = 1 after inserting.