💡 Key 4 frequency now 2, less than key 3's 3, so key 4 is more likely to be evicted next.
import time
class LFUCache:
def __init__(self, capacity: int):
self.capacity = capacity # STEP 1
self.time = 0
self.cache = {}
def get(self, key: int) -> int:
if key not in self.cache: # STEP 6,9
return -1
value, freq, _ = self.cache[key]
self.time += 1 # STEP 4,7,10,11
self.cache[key] = (value, freq + 1, self.time)
return value
def put(self, key: int, value: int) -> None:
if self.capacity == 0:
return
self.time += 1 # STEP 2,3,5,8
if key in self.cache: # STEP 4 (not used in example)
_, freq, _ = self.cache[key]
self.cache[key] = (value, freq + 1, self.time)
return
if len(self.cache) == self.capacity:
min_freq = min(freq for _, freq, _ in self.cache.values())
candidates = [k for k, (_, freq, _) in self.cache.items() if freq == min_freq]
lru_key = min(candidates, key=lambda k: self.cache[k][2])
del self.cache[lru_key]
self.cache[key] = (value, 1, self.time)
if __name__ == '__main__':
cache = LFUCache(2)
cache.put(1, 1) # STEP 2
cache.put(2, 2) # STEP 3
print(cache.get(1)) # STEP 4
cache.put(3, 3) # STEP 5
print(cache.get(2)) # STEP 6
print(cache.get(3)) # STEP 7
cache.put(4, 4) # STEP 8
print(cache.get(1)) # STEP 9
print(cache.get(3)) # STEP 10
print(cache.get(4)) # STEP 11
📊
LFU Cache - Watch the Algorithm Execute, Step by Step
Watching each operation lets you understand how frequency and recency combine to decide which key to evict, which is hard to grasp from code alone.
Step 1/11
·Active fill★Answer cell
setup
Result: []
insert
i
1
0
Result: []
insert
1
0
i
2
1
Result: []
compare
i
1
0
2
1
Result: [1]
delete
1
0
right
3
1
Result: [1,null]
compare
1
0
3
1
Result: [1,null,-1]
compare
1
0
i
3
1
Result: [1,null,-1,3]
delete
left
3
0
right
4
1
Result: [1,null,-1,3,null]
compare
3
0
4
1
Result: [1,null,-1,3,null,-1]
compare
i
3
0
4
1
Result: [1,null,-1,3,null,-1,3]
compare
3
0
i
4
1
Result: [1,null,-1,3,null,-1,3,4]
Key Takeaways
✓ LFU eviction depends on both frequency and recency, with frequency as primary and recency as tie-breaker.
This dual criteria is hard to see from code alone but becomes clear when watching eviction decisions step-by-step.
✓ Each get operation increases frequency and updates timestamp, changing eviction priority dynamically.
Seeing frequency and timestamp updates visually helps understand how usage patterns affect cache state.
✓ Eviction scans all keys to find the LFU and LRU key, demonstrating the brute force approach's cost and logic.
Watching the candidate selection and eviction clarifies why this approach is less efficient but conceptually straightforward.
Practice
(1/5)
1. Consider the following Python code implementing the optimal CustomStack with lazy increments. After executing the sequence of operations below, what is the output of the last pop() call?
pop() removes top element at index 1 (value 2). Since i=1 > 0, inc[0] += inc[1] -> inc[0] = 0 + 5 = 5. Result = stack.pop() + inc[1] = 2 + 5 = 7. inc[1] reset to 0.
Final Answer:
Option B -> Option B
Quick Check:
Increment applied lazily, pop returns 7 [OK]
Hint: Increment applies to inc array index k-1, added on pop [OK]
Common Mistakes:
Forgetting to propagate increments down on pop
Adding increments directly to popped element only
2. Given the following code snippet for flattening a multilevel doubly linked list, what is the value of curr.val after the first iteration of the outer while loop when the input list is: 1 - 2 - 3, where node 2 has a child list 4 - 5?
easy
A. 1
B. 2
C. 4
D. 3
Solution
Step 1: Initialize curr to head (node with val=1)
First iteration: curr.val = 1, no child, so move to next node.
Step 2: After first iteration, curr moves to node with val=2
Thus, after the first iteration, curr.val is 1.
Final Answer:
Option A -> Option A
Quick Check:
curr.val is 1 after first iteration of the loop [OK]
Hint: curr starts at head with val=1 and moves after processing [OK]
Common Mistakes:
Assuming curr.val changes before moving
Confusing iteration count with node value
3. Consider the following Python code implementing an LRU Cache with capacity 2. After executing the sequence of operations below, what is the return value of cache.get(1)?
After put(1,1) and put(2,2), cache has keys [2 (MRU), 1 (LRU)]. get(1) moves key 1 to MRU, order: [1,2]. put(3,3) evicts LRU key 2, cache: [3,1]. get(2) returns -1 (evicted). put(4,4) evicts LRU key 1, cache: [4,3].
Step 2: Evaluate get(1) after put(4,4)
Key 1 was evicted, so get(1) returns -1.
Final Answer:
Option A -> Option A
Quick Check:
Key 1 evicted after put(4,4), so get(1) -> -1 [OK]
Hint: Remember to update usage order on get and evict LRU on put [OK]
Common Mistakes:
Forgetting to move accessed node to front
Evicting wrong node
Returning wrong value after eviction
4. Suppose the AllOne data structure is extended to allow keys to be reused after removal (i.e., keys can be reinserted after their count reaches zero). Which modification is necessary to maintain O(1) operations and correctness?
hard
A. Do nothing special; existing code handles reuse correctly
B. Maintain a separate reuse queue to track keys that can be reinserted
C. Reset key's count to zero and keep it in the data structure to track reuse
D. Ensure keys are fully removed from all buckets and mappings upon count zero, then treat reuse as new insertion
Solution
Step 1: Understand reuse implications
Keys removed at count zero must be fully deleted to avoid stale references.
Step 2: Ensure reuse is treated as new insertion
On reuse, key is inserted fresh with count 1, requiring clean removal previously.
Step 3: Confirm O(1) operations
Full removal and fresh insertion maintain O(1) updates and correctness.
Final Answer:
Option D -> Option D
Quick Check:
Proper removal prevents duplicates and stale buckets [OK]
Hint: Full removal before reuse ensures correctness and O(1) ops [OK]
Common Mistakes:
Keeping keys with zero count in data structure causes bugs
5. Suppose the data stream allows duplicate numbers to be removed after insertion, requiring support for removeNum(num). Which modification to the balanced two heaps approach is necessary to maintain correct median retrieval?
hard
A. Use a balanced binary search tree instead of heaps to allow efficient removals.
B. Ignore removals and only add numbers, as removals break the heap structure.
C. Rebuild both heaps from scratch after every removal to maintain heap properties.
D. Add a delayed deletion map to mark elements for lazy removal and prune heaps during addNum and findMedian.
Solution
Step 1: Understand removal challenges
Heaps do not support efficient arbitrary removals, so direct removal breaks heap properties.
Step 2: Use lazy deletion
Maintain a delayed deletion map to mark elements for removal and prune them lazily during heap operations, preserving heap structure and efficiency.
Step 3: Avoid costly rebuilds
Rebuilding heaps on every removal is inefficient; ignoring removals is incorrect.
Final Answer:
Option D -> Option D
Quick Check:
Lazy deletion with pruning maintains correctness and efficiency [OK]
Hint: Lazy deletion map enables efficient removals in heaps [OK]
Common Mistakes:
Rebuilding heaps on every removal
Ignoring removals breaks correctness
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.