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 <= 109
2 * 104
calls will be made to push
and pop
.pop
.The problem requires designing a data structure that behaves like a stack but prioritizes popping the most frequent element. If multiple elements share the maximum frequency, the element pushed most recently should be popped.
Two main solutions are presented: one using a priority queue and another using hash tables.
This approach uses a hash table (cnt
) to store the frequency of each element and a priority queue (q
) to manage elements based on frequency and push order.
Data Structures:
cnt
: A hash table (dictionary in Python) mapping element values to their frequencies.q
: A priority queue (min-heap in Python, using heapq
module). Elements are tuples (-frequency, -timestamp, value)
. The negative signs ensure the highest frequency and most recent timestamp are prioritized.ts
: A timestamp counter to track the order of pushes.Algorithm:
push(val)
:
ts
).val
in cnt
.(-cnt[val], -ts, val)
onto the priority queue. The negative signs ensure that higher frequency and later timestamps have higher priority.pop()
:
cnt
.Time Complexity:
push
: O(log n), due to the priority queue insertion.pop
: O(log n), due to the priority queue removal.Space Complexity: O(n), to store the frequencies and the priority queue.
This approach uses two hash tables to achieve O(1) time complexity for both push and pop operations.
Data Structures:
cnt
: A hash table mapping elements to their frequencies.d
: A hash table mapping frequencies to stacks. Each stack stores the elements with that frequency.mx
: An integer variable storing the maximum frequency currently present.Algorithm:
push(val)
:
val
's frequency in cnt
.val
onto the stack associated with its frequency in d
.mx
if the new frequency is greater.pop()
:
mx
in d
. This is the most frequent, and most recently added element.cnt
.mx
becomes empty, decrement mx
.Time Complexity:
push
: O(1)pop
: O(1)Space Complexity: O(n)
Code Examples (Python):
Solution 1 (Priority Queue):
import heapq
from collections import defaultdict
class FreqStack:
def __init__(self):
self.cnt = defaultdict(int)
self.q = []
self.ts = 0
def push(self, val: int) -> None:
self.ts += 1
self.cnt[val] += 1
heapq.heappush(self.q, (-self.cnt[val], -self.ts, val))
def pop(self) -> int:
val = heapq.heappop(self.q)[2]
self.cnt[val] -= 1
return val
Solution 2 (Double Hash Tables):
from collections import defaultdict, deque
class FreqStack:
def __init__(self):
self.cnt = defaultdict(int)
self.d = defaultdict(deque)
self.mx = 0
def push(self, val: int) -> None:
self.cnt[val] += 1
self.d[self.cnt[val]].append(val)
self.mx = max(self.mx, self.cnt[val])
def pop(self) -> int:
val = self.d[self.mx].pop()
self.cnt[val] -= 1
if not self.d[self.mx]:
self.mx -= 1
return val
Solution 2 is generally preferred due to its superior time complexity. However, Solution 1 might be simpler to understand initially. The choice depends on the specific needs of your application. The code examples above are in Python, but similar implementations are possible in other languages like Java, C++, and Go.