Bird
Raised Fist0
Interview Prepchallenge-problemshardAmazonGoogleFacebook

LFU Cache

Choose your preparation mode4 modes available
📋
Problem

Imagine designing a caching system for a high-traffic website where you want to keep the most frequently accessed items readily available to minimize latency and server load.

Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get(key) and put(key, value). get(key) - Get the value of the key if it exists in the cache, otherwise return -1. put(key, value) - Update or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate and remove the least frequently used key before inserting a new item. If there is a tie in frequency, the least recently used key should be evicted.

0 ≤ capacity ≤ 10^50 ≤ key, value ≤ 10^9At most 2 * 10^5 calls will be made to get and put.
Edge cases: capacity = 0 → all puts should not store anything, all gets return -1put with existing key → should update value and frequencymultiple keys with same frequency → evict least recently used among them
</>
IDE
class LFUCache: def __init__(self, capacity: int): pass def get(self, key: int) -> int: pass def put(self, key: int, value: int) -> None: passpublic class LFUCache { public LFUCache(int capacity) {} public int get(int key) { return 0; } public void put(int key, int value) {} }class LFUCache { public: LFUCache(int capacity) {} int get(int key) { return 0; } void put(int key, int value) {} };class LFUCache { constructor(capacity) {} get(key) { return -1; } put(key, value) {} }
class LFUCache:
    def __init__(self, capacity: int):
        # Write your solution here
        pass
    def get(self, key: int) -> int:
        pass
    def put(self, key: int, value: int) -> None:
        pass
public class LFUCache {
    public LFUCache(int capacity) {
        // Write your solution here
    }
    public int get(int key) {
        return -1;
    }
    public void put(int key, int value) {
        // Write your solution here
    }
}
class LFUCache {
public:
    LFUCache(int capacity) {
        // Write your solution here
    }
    int get(int key) {
        return -1;
    }
    void put(int key, int value) {
        // Write your solution here
    }
};
class LFUCache {
    constructor(capacity) {
        // Write your solution here
    }
    get(key) {
        return -1;
    }
    put(key, value) {
        // Write your solution here
    }
}
Coming soon
0/9
Common Bugs to Avoid
Wrong: Evicts wrong key when multiple keys have same frequencyEviction logic ignores recency tie-break and evicts any LFU key arbitrarily✅ Maintain recency order within each frequency bucket using a linked list and evict the least recently used key among LFU keys
Wrong: get returns outdated value after put updates existing keyput does not update value or frequency for existing keys✅ In put, if key exists, update its value and increment frequency and recency accordingly
Wrong: Cache stores items despite zero capacity, get returns values instead of -1No explicit check for zero capacity in put and get✅ Return immediately in put if capacity == 0 and always return -1 in get if capacity == 0
Wrong: Solution times out on large inputEviction implemented by scanning all keys or sorting on each put✅ Use frequency buckets and doubly linked lists to achieve O(1) average time per operation
Wrong: Frequency updates off by one causing wrong eviction or get resultsFrequency increment logic is incorrect or missing on get or put✅ Increment frequency exactly once per get or put for existing keys
✓
Test Cases
t1_01basic
Input{"capacity":2,"operations":["put","put","get","put","get","get","put","get","get","get"],"arguments":[[1,1],[2,2],[1],[3,3],[2],[3],[4,4],[1],[3],[4]]}
Expected[null,null,1,null,-1,3,null,-1,3,4]

Initially cache is empty. After put(1,1) and put(2,2), cache has keys 1 and 2 with frequency 1. get(1) returns 1 and increases frequency of key 1 to 2. put(3,3) evicts key 2 because it has frequency 1 which is least. get(2) returns -1 because key 2 was evicted. get(3) returns 3 and increases frequency of key 3 to 2. put(4,4) evicts key 1 because keys 1 and 3 have frequency 2 but key 1 is least recently used. get(1) returns -1. get(3) returns 3. get(4) returns 4.

t1_02basic
Input{"capacity":3,"operations":["put","put","put","get","put","get","get","get"],"arguments":[[2,2],[1,1],[3,3],[2],[4,4],[1],[3],[4]]}
Expected[null,null,null,2,null,-1,3,4]

Cache capacity 3. After put(2,2), put(1,1), put(3,3), all keys have freq=1. get(2) returns 2 and freq(2)=2. put(4,4) evicts key 1 (freq=1, least recently used among freq=1). get(1) returns -1 (evicted). get(3) returns 3 freq=2. get(4) returns 4 freq=2.

t2_01edge
Input{"capacity":0,"operations":["put","get","put","get"],"arguments":[[1,1],[1],[2,2],[2]]}
Expected[null,-1,null,-1]

Capacity zero means cache cannot store any items. All puts do nothing, all gets return -1.

t2_02edge
Input{"capacity":1,"operations":["put","put","get","put","get"],"arguments":[[1,10],[1,20],[1],[2,30],[1]]}
Expected[null,null,20,null,-1]

Single element cache. put(1,10), then put(1,20) updates value and frequency. get(1) returns updated 20. put(2,30) evicts key 1. get(1) returns -1.

t2_03edge
Input{"capacity":2,"operations":["put","put","put","get","get"],"arguments":[[1,1],[2,2],[3,3],[1],[2]]}
Expected[null,null,null,-1,2]

After inserting keys 1 and 2, inserting key 3 evicts key 1 (freq=1, least recently used). get(1) returns -1, get(2) returns 2.

t3_01corner
Input{"capacity":2,"operations":["put","put","get","put","get","get"],"arguments":[[1,1],[2,2],[1],[3,3],[1],[3]]}
Expected[null,null,1,null,-1,3]

Greedy trap test: naive eviction by recency only. After get(1), freq(1)=2, freq(2)=1. put(3,3) evicts key 2 (lowest freq). get(1) returns -1 if eviction wrong, else 1. get(3) returns 3.

t3_02corner
Input{"capacity":2,"operations":["put","put","put","get","get","get"],"arguments":[[1,1],[2,2],[1,10],[1],[2],[3]]}
Expected[null,null,null,10,2,-1]

0/1 vs unbounded confusion test: put with existing key updates value and frequency. put(1,10) updates key 1's value and freq. get(1) returns 10, get(2) returns 2, get(3) returns -1.

t3_03corner
Input{"capacity":3,"operations":["put","put","put","get","get","put","get","get","get"],"arguments":[[1,1],[2,2],[3,3],[1],[2],[4,4],[1],[3],[4]]}
Expected[null,null,null,1,2,null,-1,3,4]

Off-by-one frequency update test: After get(1) and get(2), freq(1)=2, freq(2)=2, freq(3)=1. put(4,4) evicts key 3 (lowest freq). get(1) returns -1 if off-by-one error, else 1. get(3) returns 3 or -1 accordingly.

t4_01performance
Input{"capacity":100,"operations":["put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get","put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get","put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get","put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get","put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get","put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get"],"arguments":[[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[10,10],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11,11],[12,12],[13,13],[14,14],[15,15],[16,16],[17,17],[18,18],[19,19],[20,20],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21,21],[22,22],[23,23],[24,24],[25,25],[26,26],[27,27],[28,28],[29,29],[30,30],[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],[31,31],[32,32],[33,33],[34,34],[35,35],[36,36],[37,37],[38,38],[39,39],[40,40],[31],[32],[33],[34],[35],[36],[37],[38],[39],[40],[41,41],[42,42],[43,43],[44,44],[45,45],[46,46],[47,47],[48,48],[49,49],[50,50],[41],[42],[43],[44],[45],[46],[47],[48],[49],[50],[51,51],[52,52],[53,53],[54,54],[55,55],[56,56],[57,57],[58,58],[59,59],[60,60],[51],[52],[53],[54],[55],[56],[57],[58],[59],[60]]}
⏱ Performance - must finish in 2000ms

Large test with 100 capacity and 200 operations to verify O(1) average time complexity per operation within 2 seconds.

Practice

(1/5)
1. You need to design a system that tracks passengers checking in and out of stations, and efficiently calculates average travel times between stations. Which approach best balances time and space efficiency for frequent queries?
easy
A. Use hash maps to store running totals of travel times and counts per route.
B. Store all trips explicitly and scan them to compute averages on demand.
C. Use dynamic programming to precompute all possible route averages.
D. Use a priority queue to keep track of the shortest travel times per route.

Solution

  1. Step 1: Understand the problem requirements

    The system must support frequent check-ins, check-outs, and quick average time queries between stations.
  2. Step 2: Evaluate approaches

    Storing all trips (B) leads to slow queries. Dynamic programming (C) is not applicable as routes are dynamic and not fixed. Priority queues (D) do not help compute averages efficiently. Hash maps with running totals (A) allow O(1) updates and queries.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Hash maps maintain running sums and counts for O(1) average time queries [OK]
Hint: Running totals enable O(1) average queries [OK]
Common Mistakes:
  • Thinking storing all trips is efficient for queries
2. The following code attempts to copy a linked list with random pointers using the optimal weaving approach. Identify the line that contains a subtle bug that can cause incorrect random pointer assignment or list corruption.
def copyRandomList(head):
    if not head:
        return None
    curr = head
    # Step 1: Insert copied nodes
    while curr:
        new_node = Node(curr.val, curr.next)
        curr.next = new_node
        curr = new_node.next
    # Step 2: Assign random pointers
    curr = head
    while curr:
        if curr.random:
            curr.next.random = curr.random  # Bug here
        curr = curr.next.next
    # Step 3: Separate lists
    curr = head
    copy_head = head.next
    copy_curr = copy_head
    while curr:
        curr.next = curr.next.next
        if copy_curr.next:
            copy_curr.next = copy_curr.next.next
        curr = curr.next
        copy_curr = copy_curr.next
    return copy_head
medium
A. Line assigning curr.next.random = curr.random
B. Line inserting copied nodes: curr.next = new_node
C. Line advancing curr in Step 1: curr = new_node.next
D. Line separating original and copied lists: curr.next = curr.next.next

Solution

  1. Step 1: Analyze random pointer assignment

    The line assigns curr.next.random = curr.random, but curr.random points to original nodes, not copied nodes.
  2. Step 2: Correct assignment

    It should be curr.next.random = curr.random.next to point to the copied node corresponding to curr.random.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Incorrect random pointer assignment breaks copied list correctness [OK]
Hint: Random pointer must point to copied node, not original [OK]
Common Mistakes:
  • Assigning random pointer directly to original node
  • Mixing original and copied nodes after weaving
  • Forgetting to advance curr by two nodes
3. What is the average time complexity of search, insert, and erase operations in a skiplist with dynamic max level and probability tuning, assuming n elements?
medium
A. O(log n) average due to probabilistic multi-level structure
B. O(1) average due to direct indexing in arrays
C. O(log^2 n) because each level requires a separate traversal
D. O(n) because linked lists require linear traversal

Solution

  1. Step 1: Understand skiplist structure

    Skiplist uses multiple levels with probabilistic promotion, reducing expected search path length to logarithmic in n.
  2. Step 2: Analyze average operation cost

    Each operation traverses from top level down, with expected O(log n) steps due to geometric level distribution.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Skiplist average complexity is well-known as O(log n) [OK]
Hint: Skiplist average complexity is logarithmic, not linear or constant [OK]
Common Mistakes:
  • Assuming linear time due to linked list base
  • Confusing traversal at each level as multiplicative
  • Thinking arrays provide O(1) search in skiplist
4. Suppose the linked list with random pointers can contain cycles formed by random pointers (i.e., random pointers may create cycles independent of next pointers). Which approach correctly copies such a list without infinite loops or duplicate nodes?
hard
A. Use the optimal weaving approach as is, since it handles all cases in O(1) space.
B. Use a recursive approach with memoization to track already copied nodes and avoid infinite recursion.
C. Use the brute force approach but skip assigning random pointers to avoid cycles.
D. Modify the optimal approach to break cycles by removing random pointers before copying.

Solution

  1. Step 1: Understand cycle implications

    Random pointer cycles cause infinite loops if naive traversal is used without tracking visited nodes.
  2. Step 2: Identify safe approach

    Recursive copying with memoization tracks visited nodes, preventing infinite recursion and duplicate copies.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Memoization ensures each node is copied once even with cycles [OK]
Hint: Memoization prevents infinite recursion in cyclic graphs [OK]
Common Mistakes:
  • Assuming weaving approach handles cycles safely
  • Ignoring cycles and causing infinite loops
  • Removing random pointers loses information
5. Suppose the skyline problem is extended so that buildings can overlap and have negative heights (e.g., representing underground structures). Which modification to the divide and conquer approach is necessary to correctly handle this variant?
hard
A. Modify the merge step to consider absolute values of heights when computing max height
B. Adjust the merge logic to track both max and min heights at each x-coordinate and merge accordingly
C. No modification needed; the existing merge logic works for negative heights
D. Replace divide and conquer with a brute force height map simulation to handle negative heights

Solution

  1. Step 1: Understand impact of negative heights

    Negative heights mean skyline can go below ground, so max height alone is insufficient to represent shape.
  2. Step 2: Modify merge logic accordingly

    Need to track both max and min heights at each x to correctly merge skylines with negative and positive heights.
  3. Step 3: Evaluate other options

    Using absolute values loses sign info; brute force is inefficient; no modification fails correctness.
  4. Final Answer:

    Option B -> Option B
  5. Quick Check:

    Tracking both max and min heights handles negative overlaps correctly [OK]
Hint: Negative heights require tracking min and max heights, not just max [OK]
Common Mistakes:
  • Ignoring negative heights
  • Using absolute values incorrectly
  • Assuming no change needed
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard