class FreqStack:
def __init__(self):
self.freq = {}
self.group = {}
self.maxfreq = 0
def push(self, val: int) -> None:
f = self.freq.get(val, 0) + 1
self.freq[val] = f
if f > self.maxfreq:
self.maxfreq = f
if f not in self.group:
self.group[f] = []
self.group[f].append(val)
def pop(self) -> int:
val = self.group[self.maxfreq].pop()
self.freq[val] -= 1
if not self.group[self.maxfreq]:
self.maxfreq -= 1
return val
class FreqStack {
unordered_map<int, int> freq;
unordered_map<int, stack<int>> group;
int maxfreq = 0;
public:
FreqStack() {}
void push(int val) {
int f = ++freq[val];
if (f > maxfreq) maxfreq = f;
group[f].push(val);
}
int pop() {
int val = group[maxfreq].top();
group[maxfreq].pop();
freq[val]--;
if (group[maxfreq].empty()) maxfreq--;
return val;
}
};
class FreqStack {
Map<Integer, Integer> freq;
Map<Integer, Stack<Integer>> group;
int maxfreq;
public FreqStack() {
freq = new HashMap<>();
group = new HashMap<>();
maxfreq = 0;
}
public void push(int val) {
int f = freq.getOrDefault(val, 0) + 1;
freq.put(val, f);
if (f > maxfreq) maxfreq = f;
group.computeIfAbsent(f, z -> new Stack<>()).push(val);
}
public int pop() {
int val = group.get(maxfreq).pop();
freq.put(val, freq.get(val) - 1);
if (group.get(maxfreq).isEmpty()) maxfreq--;
return val;
}
}
var FreqStack = function() {
this.freq = new Map();
this.group = new Map();
this.maxfreq = 0;
};
FreqStack.prototype.push = function(val) {
let f = (this.freq.get(val) || 0) + 1;
this.freq.set(val, f);
if (f > this.maxfreq) this.maxfreq = f;
if (!this.group.has(f)) this.group.set(f, []);
this.group.get(f).push(val);
};
FreqStack.prototype.pop = function() {
let vals = this.group.get(this.maxfreq);
let val = vals.pop();
this.freq.set(val, this.freq.get(val) - 1);
if (vals.length === 0) this.maxfreq--;
return val;
};
You are asked to design a data structure called FreqStack that simulates a stack-like collection supporting two methods:
push(val)
: Pushes the integer val
onto the stack.pop()
: Removes and returns the most frequent element in the stack. If there is a tie for most frequent, the element closest to the top (most recently pushed) is removed and returned.push
and pop
operations (up to 100,000).pop
operation is guaranteed to be valid (the stack is never empty when pop
is called).push
and pop
work efficiently, even with many operations and possible ties in frequency.
At first glance, this problem seems like a regular stack problem, but the twist is that we need to track the most frequent element, and in case of ties, the most recently pushed among those.
A brute-force approach might be to count the frequency of each element every time we pop, then scan the stack from top to bottom to find the most frequent and most recent element. But this would be far too slow, especially with many operations.
To improve, we realize we need:
Let's break down the optimized solution step by step:
freq
to record how many times each value has been pushed but not yet popped.
group
where group[f]
is a stack (list) of all elements with frequency f
. When we push a value and its frequency increases to f
, we add it to group[f]
.
maxfreq
to always know the current highest frequency among elements in the stack.
freq[val]
.maxfreq
, update maxfreq
.val
to group[freq[val]]
.group[maxfreq]
(the most recent value with highest frequency).freq[val]
.group[maxfreq]
becomes empty, decrement maxfreq
.push
and pop
are efficient (constant time), and the tie-breaking (most recent) is handled naturally by the stack for each frequency.
Let's walk through an example:
Operations:
push(5), push(7), push(5), push(7), push(4), push(5)
pop()
(should return 5)
pop()
(should return 7)
pop()
(should return 5)
pop()
(should return 4)
Step by step:
Brute-force approach:
pop
could require scanning the entire stack to find the most frequent and most recent element.push
and pop
only does constant-time operations: map lookups, stack push/pop, and updating maxfreq
.The Maximum Frequency Stack problem is an excellent example of using multiple data structures together to achieve efficient operations. By tracking frequencies with a hash map and grouping elements by frequency in stacks, we can always access the most frequent and most recent elements in constant time. The key insight is to separate concerns: one structure for frequency, another for recency, and a simple variable for the current max frequency. This elegant combination turns a potentially slow problem into one with fast, scalable performance.