Min Stack — это стек, который умеет не только обычные операции:
push
pop
top
но ещё и операцию:
getMin
причём getMin() должен работать за O(1).
То есть задача такая:
добавить элемент O(1)
удалить верхний O(1)
посмотреть верхний O(1)
узнать минимум в стеке O(1)
Обычный стек умеет быстро доставать только верхний элемент.
Например:
stack = [5, 3, 7, 2, 6]
top() быстро вернёт 6.
Но если спросить:
какой минимум в стеке?
обычный стек сам по себе не знает. Нужно пройти все элементы:
5, 3, 7, 2, 6
и найти 2.
Это было бы:
O(n)
А Min Stack требует:
getMin() -> O(1)
Значит минимум надо как-то хранить заранее.
Главная идея
Мы храним два стека:
main stack — обычные значения
min stack — текущие минимумы
main stack хранит всё:
[5, 3, 7, 2, 6]
min stack хранит минимумы, которые актуальны на разных уровнях стека:
[5, 3, 2]
То есть когда появляется новый минимум, мы кладём его в min stack.
Пример пошагово
Делаем:
push(5)
push(3)
push(7)
push(2)
push(6)
push(5)
main = [5]
min = [5]
Минимум сейчас 5.
push(3)
3 меньше 5, значит это новый минимум.
main = [5, 3]
min = [5, 3]
Минимум сейчас 3.
push(7)
7 не меньше текущего минимума 3, значит в min не добавляем.
main = [5, 3, 7]
min = [5, 3]
Минимум всё ещё 3.
push(2)
2 меньше 3, это новый минимум.
main = [5, 3, 7, 2]
min = [5, 3, 2]
Минимум сейчас 2.
push(6)
6 не меньше 2.
main = [5, 3, 7, 2, 6]
min = [5, 3, 2]
Теперь:
getMin() -> верхушка min stack -> 2
Без прохода по всему стеку.
Что происходит при pop
Допустим:
main = [5, 3, 7, 2, 6]
min = [5, 3, 2]
Делаем:
pop()
Удаляется 6.
6 не был минимумом, значит min stack не трогаем.
main = [5, 3, 7, 2]
min = [5, 3, 2]
Теперь ещё раз:
pop()
Удаляется 2.
А 2 был текущим минимумом. Значит его надо удалить и из min stack.
main = [5, 3, 7]
min = [5, 3]
Теперь минимум снова 3.
Вот в этом вся механика.
Важный момент с дубликатами
Вот здесь есть маленькая ловушка.
Допустим:
push(5)
push(3)
push(3)
push(7)
Если в min stack класть новый минимум только когда x < currentMin, то второй
3 туда не попадёт.
Но потом если удалить один 3, можно случайно потерять минимум, хотя второй
3 ещё остался в основном стеке.
Поэтому часто делают так:
если x <= текущего минимума,
добавляем x в min stack
То есть не строго меньше, а меньше или равно.
Пример:
main = [5, 3, 3, 7]
min = [5, 3, 3]
Теперь если удалить верхний 3, в min stack останется ещё один 3.
Это правильно.
Python
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, val: int) -> None:
self.stack.append(val)
if not self.min_stack or val <= self.min_stack[-1]:
self.min_stack.append(val)
def pop(self) -> None:
val = self.stack.pop()
if val == self.min_stack[-1]:
self.min_stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.min_stack[-1]
Java
import java.util.*;
class MinStack {
private Deque<Integer> stack;
private Deque<Integer> minStack;
public MinStack() {
stack = new ArrayDeque<>();
minStack = new ArrayDeque<>();
}
public void push(int val) {
stack.push(val);
if (minStack.isEmpty() || val <= minStack.peek()) {
minStack.push(val);
}
}
public void pop() {
int val = stack.pop();
if (val == minStack.peek()) {
minStack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
Почему все операции O(1)
push -> кладём в один или два стека -> O(1)
pop -> удаляем из одного или двух стеков -> O(1)
top -> смотрим верхушку main stack -> O(1)
getMin -> смотрим верхушку min stack -> O(1)
Память:
O(n)
Потому что в худшем случае min_stack может хранить почти столько же элементов,
сколько и основной стек.
Например:
push(5)
push(4)
push(3)
push(2)
push(1)
Каждый новый элемент — новый минимум.
main = [5, 4, 3, 2, 1]
min = [5, 4, 3, 2, 1]
Это не monotonic stack
Вот важное различение.
Monotonic stack обычно используется, чтобы искать:
next greater
next smaller
previous greater
previous smaller
Там стек хранит кандидатов и выкидывает тех, кто больше не нужен.
А Min Stack — это другая идея:
обычный стек + дополнительная история минимумов
То есть Min Stack не ищет «следующий меньший элемент». Он просто поддерживает
быстрый ответ на вопрос:
какой минимум сейчас лежит внутри стека?
Интуитивная формула
Обычный стек:
верхний элемент
Min Stack:
верхний элемент + текущий минимум
min_stack — это как память о том, каким был минимум на каждом важном уровне
глубины.
Мы не пересчитываем минимум после каждого pop, а просто откатываемся к
предыдущему минимуму, потому что он уже лежит ниже в отдельном стеке.