Стек, очередь и монотонный стек

⏱ 45 мин · 🧩 заданий: 5

Стек — последним пришёл, первым ушёл

Стек (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

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Программа на Python

Правильные скобки. Строка состоит из ()[]{}. Верните 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(")(")
Задание 2 Программа на Python

Стек с минимумом. Реализуйте 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)"

Задание 3 Программа на Python

Следующая тёплая погода. Для каждого дня из списка температур верните, через сколько дней станет теплее (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)"

Задание 4 Программа на Python

Обратная польская запись. Вычислите выражение, записанное токенами в постфиксной форме: ["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
Задание 5 Заполнить таблицу

Какая структура подходит?

стекочередьдеккуча
Отмена последнего действия (Ctrl+Z)
Обход графа в ширину (BFS)
Максимум в скользящем окне за O(n)
Обработка задач по приоритету
Проверка вложенности HTML-тегов
Очередь печати документов
Отметьте ячейку — «+», пустая — «−».

Войдите, чтобы отмечать пройденные уроки.