Design a stack-like data structure to push elements to the stack and pop the most frequent element from the stack.
Implement the FreqStack class:
FreqStack() constructs an empty frequency stack.void push(int val) pushes an integer val onto the top of the stack.int pop() removes and returns the most frequent element in the stack.Example 1:
Input: ["FreqStack", "push", "push", "push", "push", "push", "push", "pop", "pop", "pop", "pop"]
[[], [5], [7], [5], [7], [4], [5], [], [], [], []]
Output: [null, null, null, null, null, null, null, 5, 7, 5, 4]Explanation:
FreqStack freqStack = new FreqStack();
freqStack.push(5); // The stack is [5]
freqStack.push(7); // The stack is [5,7]
freqStack.push(5); // The stack is [5,7,5]
freqStack.push(7); // The stack is [5,7,5,7]
freqStack.push(4); // The stack is [5,7,5,7,4]
freqStack.push(5); // The stack is [5,7,5,7,4,5]
freqStack.pop(); // return 5, as 5 is the most frequent. The stack becomes [5,7,5,7,4].
freqStack.pop(); // return 7, as 5 and 7 is the most frequent, but 7 is closest to the top. The stack becomes [5,7,5,4].
freqStack.pop(); // return 5, as 5 is the most frequent. The stack becomes [5,7,4].
freqStack.pop(); // return 4, as 4, 5 and 7 is the most frequent, but 4 is closest to the top. The stack becomes [5,7].
Constraints:
0 <= val <= 1,000,000,00020,000 calls will be made to push and pop.pop.Before attempting this problem, you should be comfortable with:
The most straightforward approach is to maintain a regular stack along with a frequency count for each element. When we need to pop, we find the maximum frequency among all elements, then scan backwards through the stack to find the most recent element with that frequency. While simple to understand, this requires scanning the entire stack on every pop operation.
cnt to track the frequency of each element and a list stack to store pushed elements.push(val): append the value to the stack and increment its count in the hash map.pop(): find the maximum frequency across all elements, then iterate from the end of the stack to find the first element with that frequency. Remove it from the stack, decrement its count, and return it.class FreqStack:
def __init__(self):
self.cnt = defaultdict(int)
self.stack = []
def push(self, val: int) -> None:
self.stack.append(val)
self.cnt[val] += 1
def pop(self) -> int:
maxCnt = max(self.cnt.values())
i = len(self.stack) - 1
while self.cnt[self.stack[i]] != maxCnt:
i -= 1
self.cnt[self.stack[i]] -= 1
return self.stack.pop(i)Where is the number of elements in the stack.
We can use a max-heap to efficiently retrieve the element that should be popped next. Each heap entry stores three pieces of information: the element's frequency, its insertion order (index), and the value itself. By prioritizing higher frequencies and then more recent insertions, the heap always gives us the correct element to pop in logarithmic time.
cnt, and an index counter starting at 0.push(val): increment the value's frequency, then push a tuple of (frequency, index, val) onto the heap. Increment the index counter.pop(): pop the top element from the heap (which has the highest frequency and most recent index among ties), decrement that value's frequency, and return the value.class FreqStack:
def __init__(self):
self.heap = []
self.cnt = defaultdict(int)
self.index = 0
def push(self, val: int) -> None:
self.cnt[val] += 1
heapq.heappush(self.heap, (-self.cnt[val], -self.index, val))
self.index += 1
def pop(self) -> int:
_, _, val = heapq.heappop(self.heap)
self.cnt[val] -= 1
return valWhere is the number of elements in the stack.
The key insight is that we can group elements by their frequency level. When an element is pushed for the first time, it goes into stack 1. When pushed again, it also goes into stack 2 (while remaining in stack 1). This way, the stack at the highest frequency level always contains the elements we should consider popping first, and the top of that stack is the most recently pushed among them.
cnt, a hash map stacks where each key is a frequency and each value is a stack, and a variable maxCnt to track the current maximum frequency.push(val): increment the value's count. If this count exceeds maxCnt, update maxCnt and create a new stack for that frequency. Push the value onto the stack at its new frequency level.pop(): pop from the stack at maxCnt, decrement that value's frequency. If the stack at maxCnt becomes empty, decrement maxCnt. Return the popped value.class FreqStack:
def __init__(self):
self.cnt = {}
self.maxCnt = 0
self.stacks = {}
def push(self, val: int) -> None:
valCnt = 1 + self.cnt.get(val, 0)
self.cnt[val] = valCnt
if valCnt > self.maxCnt:
self.maxCnt = valCnt
self.stacks[valCnt] = []
self.stacks[valCnt].append(val)
def pop(self) -> int:
res = self.stacks[self.maxCnt].pop()
self.cnt[res] -= 1
if not self.stacks[self.maxCnt]:
self.maxCnt -= 1
return resWhere is the number of elements in the stack.
Instead of using a hash map for frequency-indexed stacks, we can use a dynamic array (list). The index in the array represents the frequency level. When an element reaches a new frequency, we extend the array if needed. The last non-empty stack in the array always corresponds to the maximum frequency, eliminating the need to track maxCnt separately.
cnt and a list stacks with an empty placeholder at index 0 (since frequencies start at 1).push(val): increment the value's count. If this count equals the current length of stacks, append a new empty stack. Push the value onto the stack at index equal to its frequency.pop(): pop from the last stack in the list, decrement that value's frequency. If the last stack becomes empty, remove it from the list. Return the popped value.class FreqStack:
def __init__(self):
self.cnt = {}
self.stacks = [[]]
def push(self, val: int) -> None:
valCnt = 1 + self.cnt.get(val, 0)
self.cnt[val] = valCnt
if valCnt == len(self.stacks):
self.stacks.append([])
self.stacks[valCnt].append(val)
def pop(self) -> int:
res = self.stacks[-1].pop()
self.cnt[res] -= 1
if not self.stacks[-1]:
self.stacks.pop()
return resWhere is the number of elements in the stack.
When multiple elements share the highest frequency, the most recently pushed one must be popped first. A common mistake is to pop an arbitrary element with the maximum frequency, or to pop the first occurrence instead of the last. The heap solution handles this by storing insertion order as a secondary sort key; the stack-of-stacks solution naturally maintains recency by using stack ordering within each frequency level.
After popping an element, its frequency count must be decremented. Forgetting this step causes the data structure to believe the element still occurs at its previous frequency, leading to incorrect behavior on subsequent operations. Both the frequency map and any frequency-indexed structures must be updated consistently.
The stack-of-stacks approach tracks maxCnt to know which stack to pop from. A subtle bug occurs when the stack at maxCnt becomes empty after a pop but maxCnt is not decremented. The next pop would then try to access an empty stack. Always check if the current frequency stack is empty after popping and reduce maxCnt accordingly.
While conceptually simple, maintaining a single stack and scanning backwards to find the most frequent recent element leads to O(n) pop operations. This approach times out on large inputs. The key insight is that elements can exist at multiple frequency levels simultaneously (when pushed multiple times), which the stack-of-stacks approach leverages for O(1) operations.
In the heap-based solution, old entries remain in the heap even after frequencies change through pops. While this doesn't affect correctness (since we decrement the frequency counter separately), it can cause memory bloat. Some implementations add lazy deletion, but the simpler approach is to accept that the heap may contain stale entries whose values no longer reflect current frequencies, relying on the frequency map as the source of truth.