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.
For example:
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 <= 10^9
2 * 10^4
calls will be made to push
and pop
.pop
.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The Maximum Frequency Stack problem can be solved with a brute force approach by simulating every possible push and pop operation sequence. We would essentially keep track of the frequency of each number and then, at each pop, search through our simulated stack to find the most frequent element, prioritizing later occurrences if there's a tie.
Here's how the algorithm would work step-by-step:
class FrequencyStackBruteForce:
def __init__(self):
self.stack = []
self.frequency = {}
def push(self, value):
self.stack.append(value)
self.frequency[value] = self.frequency.get(value, 0) + 1
def pop(self):
# Find the most frequent element in stack
maximumFrequency = 0
for value in self.frequency:
maximumFrequency = max(maximumFrequency, self.frequency[value])
mostFrequentElement = None
mostFrequentIndex = -1
# Prioritize later occurrences in the stack
for index in range(len(self.stack) - 1, -1, -1):
if self.frequency[self.stack[index]] == maximumFrequency:
mostFrequentElement = self.stack[index]
mostFrequentIndex = index
break
# Remove element and decrement frequency
if mostFrequentElement is not None:
del self.stack[mostFrequentIndex]
self.frequency[mostFrequentElement] -= 1
if self.frequency[mostFrequentElement] == 0:
del self.frequency[mostFrequentElement]
return mostFrequentElement
The problem asks us to create a stack that keeps track of element frequencies. We want to efficiently retrieve the most frequent element when requested, behaving like a stack in other operations. The key idea is to use separate stacks for each frequency count to easily access the most recently pushed element of a given frequency.
Here's how the algorithm would work step-by-step:
class FrequencyStack:
def __init__(self):
self.frequency = {}
self.frequency_stacks = {}
self.maximum_frequency = 0
def push(self, value):
element_frequency = self.frequency.get(value, 0) + 1
self.frequency[value] = element_frequency
# Update maximum frequency seen so far
self.maximum_frequency = max(self.maximum_frequency, element_frequency)
if element_frequency not in self.frequency_stacks:
self.frequency_stacks[element_frequency] = []
self.frequency_stacks[element_frequency].append(value)
def pop(self):
# Get element from stack with highest frequency
popped_element = self.frequency_stacks[self.maximum_frequency].pop()
if not self.frequency_stacks[self.maximum_frequency]:
# If stack is empty, decrement maximum frequency
self.maximum_frequency -= 1
self.frequency[popped_element] -= 1
return popped_element
Case | How to Handle |
---|---|
Pushing a very large number of elements | Ensure data structures (maps, stacks) scale efficiently to avoid memory issues or performance degradation by choosing appropriate data structures. |
Popping from an empty stack | Return a predefined value (e.g., -1) or throw an exception to indicate an error state. |
Multiple elements with the same maximum frequency | The solution needs to maintain a stack per frequency, so the most recently pushed element with the maximum frequency is returned first automatically. |
Integer overflow when tracking frequency counts | Use appropriate data types (e.g., long) or handle overflow scenarios explicitly, or impose limits on input values. |
Pushing and popping elements with negative values | The solution should correctly handle negative values without any special consideration as hash maps correctly support negative keys. |
Pushing elements with frequency exceeding available memory | Impose reasonable limits on the maximum frequency or the number of elements to prevent memory exhaustion. |
Alternating push and pop operations leading to frequent stack switching | Analyze if frequent stack switching can be optimized or if it's an inherent characteristic of the input sequence. |
Pushing the same element repeatedly | The frequency counter will increment correctly, and stacks will update accordingly. |