Least Frequently Used (LFU) Cache
Company: Bytedance
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: hard
Interview Round: Onsite
Quick Answer: This question tests practical data structure design by requiring an O(1) implementation of a Least Frequently Used cache with tie-breaking by recency. It evaluates a software engineer's ability to combine hash maps and ordered collections to enforce eviction policies, a skill commonly assessed in system design and algorithm interviews.
Constraints
- 1 <= capacity <= 10^4
- 0 <= key <= 10^5
- 0 <= value <= 10^9
- At most 2 * 10^5 total calls to get and put
- get and put must each run in O(1) average time
Examples
Input: (["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get"], [[2], [1, 10], [2, 20], [1], [3, 30], [2], [3], [4, 40], [1], [4]])
Expected Output: [None, None, None, 10, None, -1, 30, None, -1, 40]
Explanation: The worked example from the prompt (capacity 2). put(3,30) evicts key 2 (lowest freq); put(4,40) hits a freq tie at 2 and evicts the least-recently-used key 1.
Input: (["LFUCache", "get"], [[1], [0]])
Expected Output: [None, -1]
Explanation: Edge case: get on an empty cache returns -1 and changes nothing.
Input: (["LFUCache", "put", "get", "put", "get", "get"], [[1], [1, 100], [1], [2, 200], [1], [2]])
Expected Output: [None, None, 100, None, -1, 200]
Explanation: Capacity 1. After put(2,200) the only slot must hold key 2, so key 1 is evicted and get(1) returns -1.
Input: (["LFUCache", "put", "put", "put", "put", "get", "get"], [[2], [1, 1], [1, 1], [1, 1], [2, 2], [1], [2]])
Expected Output: [None, None, None, None, None, 1, 2]
Explanation: Repeated put on existing key 1 only raises its frequency (no eviction). Inserting key 2 fills the cache exactly; both keys remain.
Input: (["LFUCache", "put", "put", "get", "get", "get", "put", "get", "put", "get", "get", "get"], [[3], [1, 1], [2, 2], [1], [2], [3], [3, 3], [3], [4, 4], [1], [3], [4]])
Expected Output: [None, None, None, 1, 2, -1, None, 3, None, -1, 3, 4]
Explanation: Capacity 3. After all keys reach freq 2, put(4,40) breaks the tie by LRU and evicts key 1 (touched longest ago), so the later get(1) returns -1.
Input: (["LFUCache", "put", "put", "put", "get", "get"], [[0], [0, 0], [1, 1], [2, 2], [0], [1]])
Expected Output: [None, None, None, None, -1, -1]
Explanation: Edge case: capacity 0. Every put is a no-op, so nothing is ever stored and all gets return -1.
Hints
- Keep three maps: key->value, key->frequency, and frequency->ordered set of keys. Track the current minimum frequency.
- Use an insertion-ordered container (e.g. Python OrderedDict / a doubly linked list per frequency) so that within one frequency bucket the front is the least recently used.
- Every get and every successful put 'touches' a key: remove it from its current frequency bucket, increment its frequency, and append it to the next bucket. If the emptied bucket was min_freq, bump min_freq by one.
- On insert into a full cache, evict from the min_freq bucket's front (oldest), then add the new key at frequency 1 and set min_freq back to 1.
- Handle capacity 0 (or non-positive) by making put a no-op so nothing is ever stored.