Стек, очередь и монотонный стек
Стек — последним пришёл, первым ушёл
Стек (LIFO, last in — first out) — стопка тарелок: кладём сверху, берём сверху. В Python стек — обычный list:
stack = []
stack.append("a") # push — O(1)
stack.append("b")
stack.append("c")
print(stack[-1]) # top/peek — O(1)
print(stack.pop()) # pop — O(1)
print(stack, bool(stack))
Где встречается: стек вызовов функций, отмена действий, «назад» в браузере, разбор выражений и скобок, обход в глубину без рекурсии.
Задачи на стек
Скобки
def is_valid(s):
pairs = {")": "(", "]": "[", "}": "{"}
stack = []
for ch in s:
if ch in "([{":
stack.append(ch)
elif ch in pairs:
if not stack or stack.pop() != pairs[ch]:
return False
print(f" {ch!r}: стек = {stack}")
return not stack # в конце всё должно быть закрыто
print(is_valid("{[()]}"))
print(is_valid("([)]"))
Пройдите пример 👣 Пошагово — видно, как растёт и уменьшается стек.
Упрощение пути
def simplify_path(path):
stack = []
for part in path.split("/"):
if part == "..":
if stack:
stack.pop()
elif part and part != ".":
stack.append(part)
return "/" + "/".join(stack)
print(simplify_path("/home//anna/./docs/../photos/"))
Декодирование строки
def decode(s):
"""3[a2[c]] → accaccacc"""
stack = []
current, num = "", 0
for ch in s:
if ch.isdigit():
num = num * 10 + int(ch)
elif ch == "[":
stack.append((current, num)) # запомнить, что было до скобки
current, num = "", 0
elif ch == "]":
prev, k = stack.pop()
current = prev + current * k
else:
current += ch
return current
print(decode("3[a2[c]]"), decode("2[ab]3[x]"))
Монотонный стек
Стек, в котором элементы всегда упорядочены (по убыванию или возрастанию). Решает задачи «ближайший больший/меньший элемент» за O(n): каждый элемент один раз кладётся и один раз снимается.
def next_greater(nums):
result = [-1] * len(nums)
stack = [] # индексы; значения в стеке убывают
for i, x in enumerate(nums):
while stack and nums[stack[-1]] < x: # x — следующий больший для них
result[stack.pop()] = x
stack.append(i)
return result
print(next_greater([2, 1, 2, 4, 3]))
print(next_greater([5, 4, 3, 2, 1]))
def largest_rectangle(heights):
"""Самый большой прямоугольник в гистограмме — классическая hard-задача."""
stack = [] # индексы столбцов с возрастающей высотой
best = 0
for i, h in enumerate(heights + [0]): # 0 в конце «выталкивает» всё
start = i
while stack and stack[-1][1] > h:
idx, height = stack.pop()
best = max(best, height * (i - idx))
start = idx
stack.append((start, h))
return best
print(largest_rectangle([2, 1, 5, 6, 2, 3]))
Признак задачи на монотонный стек: «для каждого элемента найти ближайший слева/справа больший/меньший», «сколько дней до…», «видимость зданий», «гистограмма».
Очередь — первым пришёл, первым ушёл
Очередь (FIFO) — очередь в кассу. В Python — collections.deque; у списка pop(0) стоит O(n).
from collections import deque
queue = deque()
queue.append("первый") # enqueue — O(1)
queue.append("второй")
queue.append("третий")
print(queue.popleft()) # dequeue — O(1)
print(queue[0], len(queue)) # peek
Где встречается: обход в ширину (BFS), очереди задач, буферы, планировщики, ограничение частоты запросов.
Очередь на двух стеках
Классический вопрос: реализовать очередь, имея только стеки.
class QueueOnStacks:
def __init__(self):
self.inbox, self.outbox = [], []
def push(self, x):
self.inbox.append(x)
def pop(self):
if not self.outbox: # перекладываем, только когда outbox пуст
while self.inbox:
self.outbox.append(self.inbox.pop())
return self.outbox.pop()
q = QueueOnStacks()
for x in [1, 2, 3]:
q.push(x)
print(q.pop(), q.pop())
q.push(4)
print(q.pop(), q.pop())
Каждый элемент перекладывается один раз → амортизированно O(1) на операцию.
Дек и скользящий максимум
deque — двусторонняя очередь: O(1) с обоих концов. Классическая задача — максимум в каждом окне размера k за O(n) (монотонная очередь):
from collections import deque
def window_max(nums, k):
dq = deque() # индексы; значения убывают
result = []
for i, x in enumerate(nums):
while dq and nums[dq[-1]] <= x: # меньшие элементы больше никогда не станут максимумом
dq.pop()
dq.append(i)
if dq[0] <= i - k: # вышел из окна
dq.popleft()
if i >= k - 1:
result.append(nums[dq[0]])
return result
print(window_max([1, 3, -1, -3, 5, 3, 6, 7], 3))
Итог
| Структура | В Python | Операции O(1) | Задачи |
|---|---|---|---|
| Стек | list |
append, pop, [-1] |
скобки, выражения, отмена, DFS |
| Очередь | deque |
append, popleft |
BFS, задачи по порядку |
| Дек | deque |
оба конца | скользящий максимум |
| Монотонный стек | list |
каждый элемент один раз | ближайший больший/меньший |
Практика решено 0 из 5
Правильные скобки. Строка состоит из ()[]{}. Верните True, если скобки расставлены правильно:
каждая открывающая закрыта скобкой того же типа и в правильном порядке.
Какие тесты проверяют код (2)
assert is_valid("()[]{}") and is_valid("{[()]}") and is_valid("")
assert not is_valid("(]") and not is_valid("([)]") and not is_valid("((") and not is_valid(")(")
def is_valid(s):
pairs = {")": "(", "]": "[", "}": "{"}
stack = []
for ch in s:
if ch in pairs:
if not stack or stack.pop() != pairs[ch]:
return False
else:
stack.append(ch)
return not stack
Стек с минимумом. Реализуйте MinStack с методами push(x), pop(), top() и get_min(),
каждый — за O(1). Подсказка: храните рядом с каждым элементом текущий минимум.
Какие тесты проверяют код (2)
s = MinStack()
for x in [5, 3, 7, 3, 1]:
s.push(x)
assert s.get_min() == 1 and s.pop() == 1 and s.get_min() == 3 and s.top() == 3
s.pop()
assert s.get_min() == 3 and s.top() == 7
s.pop(); s.pop()
assert s.get_min() == 5 and s.top() == 5
import time
s = MinStack()
for x in range(5_000, 0, -1):
s.push(x)
start = time.perf_counter()
for _ in range(2_000):
s.get_min()
assert time.perf_counter() - start < 0.02, "get_min должен работать за O(1)"
class MinStack:
def __init__(self):
self.items = [] # пары (значение, минимум на этот момент)
def push(self, x):
current = min(x, self.items[-1][1]) if self.items else x
self.items.append((x, current))
def pop(self):
return self.items.pop()[0]
def top(self):
return self.items[-1][0]
def get_min(self):
return self.items[-1][1]
Следующая тёплая погода. Для каждого дня из списка температур верните, через сколько дней
станет теплее (0, если такого дня нет). [73, 74, 75, 71, 69, 72, 76, 73] →
[1, 1, 4, 2, 1, 1, 0, 0]. Монотонный стек, O(n).
Какие тесты проверяют код (3)
assert daily_temperatures([73, 74, 75, 71, 69, 72, 76, 73]) == [1, 1, 4, 2, 1, 1, 0, 0] assert daily_temperatures([30, 40, 50, 60]) == [1, 1, 1, 0] and daily_temperatures([]) == [] import time temps = list(range(4_000, 0, -1)) start = time.perf_counter() assert daily_temperatures(temps) == [0] * 4_000 assert time.perf_counter() - start < 0.1, "Нужен монотонный стек — O(n)"
def daily_temperatures(temps):
result = [0] * len(temps)
stack = [] # индексы дней, ждущих тёплого дня
for i, t in enumerate(temps):
while stack and temps[stack[-1]] < t:
j = stack.pop()
result[j] = i - j
stack.append(i)
return result
Обратная польская запись. Вычислите выражение, записанное токенами в постфиксной форме:
["2", "1", "+", "3", "*"] → (2 + 1) * 3 = 9. Операции + - * /; деление — целочисленное
с округлением к нулю (int(a / b)).
Какие тесты проверяют код (3)
assert eval_rpn(["2", "1", "+", "3", "*"]) == 9 and eval_rpn(["4", "13", "5", "/", "+"]) == 6 assert eval_rpn(["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]) == 22 assert eval_rpn(["7", "-3", "/"]) == -2
def eval_rpn(tokens):
stack = []
for tok in tokens:
if tok in "+-*/" and len(tok) == 1:
b, a = stack.pop(), stack.pop()
if tok == "+":
stack.append(a + b)
elif tok == "-":
stack.append(a - b)
elif tok == "*":
stack.append(a * b)
else:
stack.append(int(a / b))
else:
stack.append(int(tok))
return stack[0]
Какая структура подходит?
| стек | очередь | дек | куча | |
|---|---|---|---|---|
| Отмена последнего действия (Ctrl+Z) | ||||
| Обход графа в ширину (BFS) | ||||
| Максимум в скользящем окне за O(n) | ||||
| Обработка задач по приоритету | ||||
| Проверка вложенности HTML-тегов | ||||
| Очередь печати документов |
Войдите, чтобы отмечать пройденные уроки.