Implement Min Stack
Problem Description
Design a stack that supports standard stack operations (push, pop, top) and also supports retrieving the minimum element in the stack in constant time.
Core Idea
The key challenge is how to quickly (in O(1) time) know which element is the current minimum in the stack after each push and pop operation. If only one stack is used, retrieving the minimum would require traversing all elements each time, resulting in O(n) time complexity. To achieve O(1) time complexity for the getMin operation, we need extra space to record the minimum value at each moment.
Solution: Auxiliary Stack Method
The most classic and intuitive solution is to use an auxiliary stack (min stack) to synchronously record the minimum value for each state of the main stack.
-
Data Structure Design
data_stack: The main stack, used to store all pushed elements normally.min_stack: The auxiliary stack (min stack), whose top always records the minimum value among all elements in the current main stack.
-
Detailed Push Operation
- Step 1: Push the new element
xonto the main stackdata_stack. - Step 2: Handle the min stack
min_stack. This is the most crucial step, with two equivalent strategies:- Strategy A (Synchronous Push): If the min stack is empty, then
xis the current minimum, simply pushxonto the min stack. If the min stack is not empty, comparexwith the current top element of the min stack, and push the smaller one onto the min stack.min_value = min(x, min_stack[-1])min_stack.push(min_value)
- Strategy B (Asynchronous, More Space-Efficient): Push
xonto the min stack only if the min stack is empty, or ifxis less than or equal to the current top element of the min stack. Otherwise, the min stack remains unchanged.
- Strategy A (Synchronous Push): If the min stack is empty, then
- Comparison of the Two Strategies:
- Strategy A has the advantage of very uniform logic; the main stack and min stack always have the same number of elements, making implementation simple. The drawback is that when multiple elements larger than the current minimum are pushed consecutively, the min stack stores many duplicate, identical minimum values, resulting in slightly lower space efficiency.
- Strategy B has the advantage of higher space efficiency, as the min stack only stores "stage-wise" minimum values. The drawback is that the logic for the pop operation needs to correspond to the push strategy.
- Example Illustrating Strategy B: Push 3, 4, 2, 1 sequentially.
- push(3): Main stack
[3], min stack empty, push 3 ->[3]. - push(4): Main stack
[3, 4], 4 > 3, min stack unchanged ->[3]. - push(2): Main stack
[3, 4, 2], 2 < 3, push 2 ->[3, 2]. - push(1): Main stack
[3, 4, 2, 1], 1 < 2, push 1 ->[3, 2, 1].
- push(3): Main stack
- Step 1: Push the new element
-
Detailed Pop Operation
- Step 1: Pop the top element from the main stack
data_stack, denoted aspopped_value. - Step 2: Handle the min stack
min_stack. The operation here must strictly correspond to the strategy used for the push operation.- If Strategy A was used for push: Simply pop the top of the min stack, because the two stacks always have the same height.
- If Strategy B was used for push: It is necessary to check whether the popped main stack element
popped_valueis equal to the current top element of the min stack. If they are equal, it means this popped element is a "copy" of the current minimum value, so the top of the min stack also needs to be popped. Otherwise, the min stack remains unchanged.
- Continuing the Previous Example (Strategy B): Current stack state: Main stack
[3,4,2,1], Min stack[3,2,1].- pop(): Main stack pops 1. 1 equals the min stack top 1, so min stack also pops 1. Now main stack
[3,4,2], min stack[3,2], current minimum is 2. - pop(): Main stack pops 2. 2 equals the min stack top 2, so min stack also pops 2. Now main stack
[3,4], min stack[3], current minimum is 3.
- pop(): Main stack pops 1. 1 equals the min stack top 1, so min stack also pops 1. Now main stack
- Step 1: Pop the top element from the main stack
-
Top Operation
- Directly return the top element of the main stack
data_stack. This operation does not affect the min stack.
- Directly return the top element of the main stack
-
GetMin Operation
- Directly return the top element of the auxiliary stack
min_stack. Because we carefully maintain the min stack during push and pop operations, its top always represents the current minimum value in the main stack, so the time complexity of this operation is O(1).
- Directly return the top element of the auxiliary stack
Code Implementation (Based on Strategy B, Space-Optimized Version)
The following is an implementation example in Python.
class MinStack:
def __init__(self):
# Initialize main stack and min stack
self.data_stack = []
self.min_stack = []
def push(self, val: int) -> None:
# Always push element onto the main stack
self.data_stack.append(val)
# Push onto min stack only if min stack is empty, or new value is <= current minimum
# Note: The "less than or equal to" check handles cases with multiple identical minimum values
if not self.min_stack or val <= self.min_stack[-1]:
self.min_stack.append(val)
def pop(self) -> None:
# Pop element from the main stack
popped_value = self.data_stack.pop()
# If the popped element exactly equals the current min stack top (i.e., current minimum), pop min stack too
if popped_value == self.min_stack[-1]:
self.min_stack.pop()
def top(self) -> int:
# Return the top element of the main stack
return self.data_stack[-1]
def getMin(self) -> int:
# Return the top element of the min stack, which is the current minimum
return self.min_stack[-1]
Complexity Analysis
- Time Complexity: All operations (push, pop, top, getMin) have O(1) time complexity.
- Space Complexity: O(n), where n is the total number of operations. In the worst case (all elements strictly decreasing), the main stack and min stack occupy the same space; on average, the min stack is smaller than the main stack, offering better space efficiency than the dual-stack synchronous Strategy A.