Facebook Pixel

895. Maximum Frequency Stack

HardStackDesignHash TableOrdered Set
LeetCode ↗

Problem Description

You need to design a special stack data structure called FreqStack that supports two operations:

  1. Push: Add an element to the stack
  2. Pop: Remove and return the element that appears most frequently in the stack

The key challenge is that when you pop, you don't just remove any occurrence of the most frequent element - you need to follow specific rules:

  • Find the element(s) with the highest frequency count
  • If multiple elements share the same highest frequency, remove the one that was pushed most recently (closest to the top)

For example, if you push the sequence [5, 7, 5, 7, 4, 5]:

  • Element 5 appears 3 times (highest frequency)
  • Elements 7 appears 2 times
  • Element 4 appears 1 time

When you call pop(), it should return 5 because it has the highest frequency. The specific 5 removed would be the last one pushed.

If after that pop you have [5, 7, 5, 7, 4] and call pop() again:

  • Both 5 and 7 now appear 2 times (tied for highest frequency)
  • Since 7 was pushed more recently than the remaining 5, it returns 7

The implementation uses:

  • A hash table cnt to track the frequency of each element
  • A max heap q that stores tuples of (-frequency, -timestamp, value) to efficiently find the most frequent and most recent element
  • A timestamp counter ts to track the order of insertions

The negative values in the heap ensure that higher frequencies and more recent timestamps have priority when popping elements.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Design + Supporting Data Structures?

This problem maps to Design + Supporting Data Structures through a short path in the full flowchart.

Parsesymbols?noObjectwithoperationyesDesign +Supporting DataStructures

Maintain a frequency map plus a stack-per-frequency, so push grows the right stack and pop pulls from the highest-frequency stack to break ties by recency.

Open in Flowchart

Intuition

When thinking about this problem, we need to handle two key requirements: tracking frequency and maintaining recency when frequencies are tied.

The first thought might be to use a simple frequency counter, but that alone won't help us efficiently find the element with maximum frequency. We'd have to scan through all elements every time we pop, which would be inefficient.

This naturally leads us to consider a heap (priority queue) since it can give us the maximum element in O(log n) time. But what should we store in the heap?

We need to prioritize by frequency first, so that should be our primary sorting criterion. However, when frequencies are equal, we need the most recently pushed element. This means we need to track when each element was pushed - hence the need for timestamps.

The clever insight is to combine both frequency and timestamp into a single tuple for the heap. By using (-frequency, -timestamp, value), we create a max heap where:

  • Higher frequencies come first (more negative means higher priority in a min heap)
  • Among equal frequencies, larger timestamps come first (more recent pushes)

Why don't we remove elements from the heap when their frequency decreases? This would require searching through the heap, which is expensive. Instead, we use a "lazy deletion" approach - we keep all entries in the heap and only check if they're valid when we pop them. The frequency counter cnt tells us the current actual frequency of each element.

When we pop, we get the top element from the heap. Since we decrement its frequency in cnt, any outdated entries for this element in the heap (with higher frequencies) will naturally be ignored in future operations because we always use the most recent frequency when pushing new entries.

This approach elegantly handles both requirements with O(log n) complexity for both push and pop operations.

Pattern Learn more about Stack patterns.

New to patterns?Most interview problems reduce to a handful of patterns. This video covers the main eight.

Solution Approach

The solution uses a hash table combined with a priority queue (max heap) to efficiently track and retrieve the most frequent elements.

Data Structures Used:

  1. Hash Table cnt: A defaultdict(int) that maintains the current frequency count for each element in the stack.

  2. Priority Queue q: A max heap implemented using Python's heapq (which is a min heap by default, so we use negative values). Each entry is a tuple (-frequency, -timestamp, value).

  3. Timestamp Counter ts: An integer that increments with each push operation to track the order of insertions.

Implementation Details:

Initialization (__init__):

  • Initialize cnt as an empty frequency counter
  • Initialize q as an empty list (will be used as a heap)
  • Set ts = 0 as the starting timestamp

Push Operation (push):

  1. Increment the timestamp: self.ts += 1
  2. Update the frequency count: self.cnt[val] += 1
  3. Add a new entry to the heap: heappush(self.q, (-self.cnt[val], -self.ts, val))
    • The negative frequency ensures higher frequencies have priority
    • The negative timestamp ensures more recent elements have priority when frequencies are equal
    • The actual value is stored as the third element

Pop Operation (pop):

  1. Extract the top element from the heap: val = heappop(self.q)[2]
    • This gives us the element with the highest frequency
    • If frequencies are tied, it gives us the most recently pushed one
  2. Decrement the frequency count: self.cnt[val] -= 1
  3. Return the popped value: return val

Key Insights:

  • No Stale Entries: Each heap entry stands for one copy of a value that is still in the stack. If 5 was pushed three times, the heap holds (-1, ..., 5), (-2, ..., 5), and (-3, ..., 5). Popping removes the (-3, ..., 5) entry and lowers cnt[5] to 2, and the remaining entry with frequency 2 now describes the current top copy of 5 exactly. Because pops always remove the highest-frequency copy of a value first, the heap never needs cleanup.

  • Time Complexity:

    • Push: O(log n) where n is the number of entries in the heap
    • Pop: O(log n) for the heap operation
  • Space Complexity: O(n) where n is the number of elements currently in the stack (at most the total number of pushes), since the heap holds one entry per element.

Example Walkthrough

Let's walk through a concrete example with the sequence of operations:

  • push(4), push(5), push(4), push(6), push(5), push(4), pop(), pop()

Initial State:

  • cnt = {} (empty frequency counter)
  • q = [] (empty heap)
  • ts = 0 (timestamp counter)

Step 1: push(4)

  • Increment timestamp: ts = 1
  • Update frequency: cnt[4] = 1
  • Add to heap: heappush(q, (-1, -1, 4))
  • State: cnt = {4: 1}, q = [(-1, -1, 4)]

Step 2: push(5)

  • Increment timestamp: ts = 2
  • Update frequency: cnt[5] = 1
  • Add to heap: heappush(q, (-1, -2, 5))
  • State: cnt = {4: 1, 5: 1}, q = [(-1, -2, 5), (-1, -1, 4)]
    • Note: 5 has higher priority due to more recent timestamp

Step 3: push(4)

  • Increment timestamp: ts = 3
  • Update frequency: cnt[4] = 2
  • Add to heap: heappush(q, (-2, -3, 4))
  • State: cnt = {4: 2, 5: 1}, q = [(-2, -3, 4), (-1, -1, 4), (-1, -2, 5)]
    • The heap now has 4 at the top (frequency 2 beats frequency 1)

Step 4: push(6)

  • Increment timestamp: ts = 4
  • Update frequency: cnt[6] = 1
  • Add to heap: heappush(q, (-1, -4, 6))
  • State: cnt = {4: 2, 5: 1, 6: 1}, heap has 4 with frequency 2 at top

Step 5: push(5)

  • Increment timestamp: ts = 5
  • Update frequency: cnt[5] = 2
  • Add to heap: heappush(q, (-2, -5, 5))
  • State: cnt = {4: 2, 5: 2, 6: 1}, heap has 5 with (-2, -5) at top
    • Both 4 and 5 have frequency 2, but 5 was pushed more recently

Step 6: push(4)

  • Increment timestamp: ts = 6
  • Update frequency: cnt[4] = 3
  • Add to heap: heappush(q, (-3, -6, 4))
  • State: cnt = {4: 3, 5: 2, 6: 1}, heap has 4 with frequency 3 at top

Step 7: pop()

  • Extract from heap: heappop(q) returns (-3, -6, 4), so val = 4
  • Decrement frequency: cnt[4] = 2
  • Return: 4
  • State: cnt = {4: 2, 5: 2, 6: 1}
    • Note: The entries (-2, -3, 4) and (-1, -1, 4) remain in the heap; they stand for the two copies of 4 that are still in the stack

Step 8: pop()

  • Extract from heap: Top is (-2, -5, 5) (5 with frequency 2, timestamp 5)
    • This beats (-2, -3, 4) (4 with frequency 2, timestamp 3) because 5 is more recent
  • Pop returns val = 5
  • Decrement frequency: cnt[5] = 1
  • Return: 5

The heap holds exactly one entry per element still in the stack, and the combination of frequency and timestamp ensures each pop returns the correct element.

Solution Implementation

1from collections import defaultdict
2from heapq import heappush, heappop
3from typing import Optional
4
5
6class FreqStack:
7    """
8    A stack-like data structure that pops the most frequently occurring element.
9    When multiple elements have the same frequency, the most recently pushed element is popped.
10    """
11  
12    def __init__(self):
13        """Initialize the frequency stack with necessary data structures."""
14        # Dictionary to track the frequency count of each element
15        self.frequency_count = defaultdict(int)
16      
17        # Min heap (priority queue) to maintain elements by frequency and recency
18        # Stored as tuples: (-frequency, -timestamp, value)
19        # Negative values used to simulate max heap behavior
20        self.priority_queue = []
21      
22        # Timestamp counter to track insertion order
23        self.timestamp = 0
24
25    def push(self, val: int) -> None:
26        """
27        Push an element onto the frequency stack.
28      
29        Args:
30            val: The integer value to push onto the stack
31        """
32        # Increment timestamp for this operation
33        self.timestamp += 1
34      
35        # Update frequency count for this value
36        self.frequency_count[val] += 1
37      
38        # Add to priority queue with negative frequency and timestamp for max heap behavior
39        # Priority order: highest frequency first, then most recent timestamp
40        heappush(self.priority_queue, 
41                (-self.frequency_count[val], -self.timestamp, val))
42
43    def pop(self) -> int:
44        """
45        Pop and return the most frequent element from the stack.
46        If there's a tie in frequency, return the most recently pushed element.
47      
48        Returns:
49            The popped integer value
50        """
51        # Extract the value with highest priority (most frequent, most recent)
52        _, _, value = heappop(self.priority_queue)
53      
54        # Decrease the frequency count for this value
55        self.frequency_count[value] -= 1
56      
57        return value
58
59
60# Your FreqStack object will be instantiated and called as such:
61# obj = FreqStack()
62# obj.push(val)
63# param_2 = obj.pop()
64
1class FreqStack {
2    // Map to track the frequency count of each element
3    private Map<Integer, Integer> frequencyMap = new HashMap<>();
4  
5    // Priority queue to maintain elements based on frequency and insertion order
6    // Each array contains: [frequency, timestamp, value]
7    // Ordering: higher frequency first, then more recent timestamp first
8    private PriorityQueue<int[]> priorityQueue = new PriorityQueue<>((a, b) -> {
9        if (a[0] == b[0]) {
10            // Same frequency: compare by timestamp (larger timestamp = more recent)
11            return b[1] - a[1];
12        }
13        // Different frequency: higher frequency comes first
14        return b[0] - a[0];
15    });
16  
17    // Timestamp counter to track insertion order
18    private int timestamp;
19
20    /**
21     * Constructor to initialize the FreqStack
22     */
23    public FreqStack() {
24    }
25
26    /**
27     * Pushes an element onto the stack
28     * @param val the value to be pushed
29     */
30    public void push(int val) {
31        // Update the frequency count for this value
32        frequencyMap.put(val, frequencyMap.getOrDefault(val, 0) + 1);
33      
34        // Add to priority queue with current frequency, timestamp, and value
35        priorityQueue.offer(new int[] {frequencyMap.get(val), ++timestamp, val});
36    }
37
38    /**
39     * Removes and returns the most frequent element from the stack
40     * If there is a tie for most frequent element, the most recently pushed element is removed
41     * @return the popped value
42     */
43    public int pop() {
44        // Remove the element with highest priority (highest frequency, most recent)
45        int value = priorityQueue.poll()[2];
46      
47        // Decrease the frequency count for this value
48        frequencyMap.put(value, frequencyMap.get(value) - 1);
49      
50        return value;
51    }
52}
53
54/**
55 * Your FreqStack object will be instantiated and called as such:
56 * FreqStack obj = new FreqStack();
57 * obj.push(val);
58 * int param_2 = obj.pop();
59 */
60
1class FreqStack {
2public:
3    FreqStack() {
4        // Constructor - no initialization needed as member variables are already initialized
5    }
6
7    void push(int val) {
8        // Increment the frequency count for this value
9        ++frequency[val];
10      
11        // Push a tuple to the priority queue containing:
12        // 1. frequency[val]: current frequency of the value (higher frequency = higher priority)
13        // 2. ++timestamp: incrementing timestamp (later pushed = higher priority for same frequency)
14        // 3. val: the actual value
15        maxHeap.emplace(frequency[val], ++timestamp, val);
16    }
17
18    int pop() {
19        // Get the top element from the priority queue (most frequent, most recent)
20        auto [freq, time, value] = maxHeap.top();
21        maxHeap.pop();
22      
23        // Decrement the frequency count for this value
24        --frequency[value];
25      
26        return value;
27    }
28
29private:
30    // Maps each value to its current frequency in the stack
31    unordered_map<int, int> frequency;
32  
33    // Max heap that stores tuples of (frequency, timestamp, value)
34    // Elements are ordered by frequency first, then by timestamp
35    priority_queue<tuple<int, int, int>> maxHeap;
36  
37    // Global timestamp counter to track insertion order
38    int timestamp = 0;
39};
40
41/**
42 * Your FreqStack object will be instantiated and called as such:
43 * FreqStack* obj = new FreqStack();
44 * obj->push(val);
45 * int param_2 = obj->pop();
46 */
47
1// Maps each value to its current frequency in the stack
2const frequency: Map<number, number> = new Map();
3
4// Array of tuples storing [frequency, timestamp, value]
5// We'll manually maintain this as a max heap
6const maxHeap: Array<[number, number, number]> = [];
7
8// Global timestamp counter to track insertion order
9let timestamp: number = 0;
10
11// Helper function to compare heap elements
12// Returns true if a should have higher priority than b
13function compareTuples(a: [number, number, number], b: [number, number, number]): boolean {
14    // First compare by frequency (higher frequency = higher priority)
15    if (a[0] !== b[0]) {
16        return a[0] > b[0];
17    }
18    // If frequencies are equal, compare by timestamp (later timestamp = higher priority)
19    return a[1] > b[1];
20}
21
22// Helper function to maintain heap property after insertion
23function heapifyUp(index: number): void {
24    while (index > 0) {
25        const parentIndex = Math.floor((index - 1) / 2);
26        if (compareTuples(maxHeap[index], maxHeap[parentIndex])) {
27            // Swap with parent
28            [maxHeap[index], maxHeap[parentIndex]] = [maxHeap[parentIndex], maxHeap[index]];
29            index = parentIndex;
30        } else {
31            break;
32        }
33    }
34}
35
36// Helper function to maintain heap property after removal
37function heapifyDown(index: number): void {
38    const length = maxHeap.length;
39    while (true) {
40        let largest = index;
41        const leftChild = 2 * index + 1;
42        const rightChild = 2 * index + 2;
43      
44        if (leftChild < length && compareTuples(maxHeap[leftChild], maxHeap[largest])) {
45            largest = leftChild;
46        }
47        if (rightChild < length && compareTuples(maxHeap[rightChild], maxHeap[largest])) {
48            largest = rightChild;
49        }
50      
51        if (largest !== index) {
52            // Swap with largest child
53            [maxHeap[index], maxHeap[largest]] = [maxHeap[largest], maxHeap[index]];
54            index = largest;
55        } else {
56            break;
57        }
58    }
59}
60
61function push(val: number): void {
62    // Increment the frequency count for this value
63    const currentFreq = (frequency.get(val) || 0) + 1;
64    frequency.set(val, currentFreq);
65  
66    // Increment timestamp for this insertion
67    timestamp++;
68  
69    // Add tuple to the heap containing:
70    // 1. currentFreq: current frequency of the value (higher frequency = higher priority)
71    // 2. timestamp: current timestamp (later pushed = higher priority for same frequency)
72    // 3. val: the actual value
73    maxHeap.push([currentFreq, timestamp, val]);
74  
75    // Maintain heap property
76    heapifyUp(maxHeap.length - 1);
77}
78
79function pop(): number {
80    // Get the top element from the heap (most frequent, most recent)
81    const [freq, time, value] = maxHeap[0];
82  
83    // Move last element to top and remove the last element
84    maxHeap[0] = maxHeap[maxHeap.length - 1];
85    maxHeap.pop();
86  
87    // Maintain heap property if heap is not empty
88    if (maxHeap.length > 0) {
89        heapifyDown(0);
90    }
91  
92    // Decrement the frequency count for this value
93    const currentFreq = frequency.get(value)! - 1;
94    if (currentFreq === 0) {
95        frequency.delete(value);
96    } else {
97        frequency.set(value, currentFreq);
98    }
99  
100    return value;
101}
102

Time and Space Complexity

Time Complexity:

  • __init__(): O(1) - Initializing empty data structures takes constant time.
  • push(val): O(log n) - Incrementing the counter and timestamp are O(1) operations, but inserting into the heap using heappush takes O(log n) time where n is the number of elements in the heap.
  • pop(): O(log n) - Removing the top element from the heap using heappop takes O(log n) time, and decrementing the counter is O(1).

Space Complexity:

  • O(n) where n is the total number of push operations performed.
  • The heap q stores one entry for each push operation, never removing entries until they are popped, so it can grow up to size n.
  • The counter dictionary cnt stores at most O(m) entries where m is the number of unique values pushed, which is bounded by n.
  • The timestamp variable ts uses O(1) space.
  • Overall space complexity is O(n) as the heap dominates the space usage.

Note: The heap stores tuples of (-frequency, -timestamp, value) using negative values to achieve max-heap behavior since Python's heapq implements a min-heap. This ensures elements with higher frequency are popped first, and among elements with the same frequency, the most recently pushed element (larger timestamp) is popped first.

Pattern Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Breaking Frequency Ties by Value Instead of Recency

The Problem: The heap key must break ties between equal frequencies by push order. A version that stores (-frequency, -value) and drops the timestamp picks the larger value instead of the most recent one.

# WRONG: ties are broken by value, not by push order
heappush(self.priority_queue, (-self.frequency_count[val], -val))

With push(2), push(1), pop(), both values have frequency 1. The correct answer is 1 (pushed last), but this version returns 2.

Solution: Store a timestamp that increases with every push and use -timestamp as the second key, as the solution does: (-frequency, -timestamp, val).

Pitfall 2: Assuming the Heap Fills Up with Stale Entries

The Problem: It is easy to assume that after a pop, the older entries of the same value (with lower frequencies) are outdated and must be cleaned up or validated. They are not. Push 5 three times: the heap holds entries with frequencies 1, 2, and 3. A pop removes the frequency-3 entry and sets cnt[5] = 2, and the remaining frequency-2 entry describes the current top copy of 5 exactly. Each heap entry always matches one element that is still in the stack, so the heap size equals the stack size and no cleanup step is needed.

Extra code such as rebuilding the heap with one entry per value is actually harmful: it drops the entries for the lower copies of each value, so later pops return the wrong elements.

Pitfall 3: Using a Heap When an O(1) Design Exists

The Problem: The heap gives O(log n) per operation. A design based on one stack per frequency level achieves O(1) per operation:

class FreqStack:
    def __init__(self):
        self.freq = {}  # element -> frequency
        self.group = defaultdict(list)  # frequency -> stack of elements
        self.max_freq = 0
  
    def push(self, val: int) -> None:
        freq = self.freq.get(val, 0) + 1
        self.freq[val] = freq
        self.group[freq].append(val)
        self.max_freq = max(self.max_freq, freq)
  
    def pop(self) -> int:
        val = self.group[self.max_freq].pop()
        self.freq[val] -= 1
        if not self.group[self.max_freq]:
            self.max_freq -= 1
        return val

A value with frequency f appears once in each of the stacks group[1] through group[f], so popping from group[max_freq] always returns the most recent element among those with the highest frequency.

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

You are given an array of intervals where intervals[i] = [start_i, end_i] represent the start and end of the ith interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More