Задача: создать стек с помощью очередей
Решаем задачи, которые дают программистам на собеседованиях в IT-компаниях. Сегодня пытаемся понять, как сделать стек из нескольких очередей.
Иллюстрация: Polina Vari для Skillbox Media
Сергей Голицын
Senior Java Developer в Covalent Inc. и преподаватель. Больше семи лет в Java-разработке. В свободное время судит хакатоны и делится опытом с начинающими программистами. Пишет статьи для «Хабра» и Medium. Ведёт телеграм-каналы «Полезные ссылки около Java» и Cracking code interview.
Условие. Реализовать стек last-in-first-out (LIFO), используя максимум две очереди. Он должен поддерживать все стандартные функции обычного стека — push, pop, top и empty:
- push — добавляет элемент на вершину стека;
- pop — убирает элемент с вершины и возвращает его как результат;
- top — возвращает элемент с вершины стека, но не убирает его;
- empty — возвращает true, если стек пустой, иначе — false.
Примечание: вы должны использовать только стандартные операции с очередями — push, peek/pop, size и empty. А если ваш язык не поддерживает очереди, то их можно реализовать через обычный список или двухстороннюю очередь, но при этом использовать только функции, приведённые выше.
Ввод и вывод. Пример
Решить эту задачу самостоятельно и на разных языках программирования можно на LeetCode. Наше решение взято из телеграм-канала Сергея Cracking code interview.
Решение
Первый вариант. Мы решим эту задачу двумя способами. Первый будет использовать две очереди, а второй — одну. Начнём с первого.
В этом варианте нам понадобится дополнительная память. Сначала создадим класс MyStack с двумя очередями current и tmp:
Теперь давайте реализуем метод push:
Всё просто — мы добавляем новый элемент в очередь current. Но главный трюк будет с методом pop. Чтобы он заработал, мы должны при его вызове положить все элементы, кроме последнего, в очередь tmp. А единственный элемент из очереди current мы вернём как результат.
Давайте напишем его:
Метод top будет похож на метод pop:
А для метода empty нам просто нужно проверить, пуста ли очередь current:
Второй вариант. В этом случае нам не понадобится дополнительная память. Мы ограничимся всего одной очередью.
Вот так выглядит наш класс:
Чтобы реализовать метод push, нам нужно добавлять новый элемент в очередь и после этого перемещать все прочие элементы в конец этой же очереди.
Например, если у нас есть очередь [2, 1] и мы добавляем элемент 3, то получаем очередь [3, 2, 1]. Но теперь мы берём последовательно все элементы, кроме 3, и добавляем их в конец: [3, 2, 1] → [1, 3, 2] → [2, 1, 3].
Получится такая функция:
А для методов pop, top и empty мы будем использовать встроенные функции:
Результаты
Временная сложность. Вариант с двумя очередями: push — O(1), pop — O(N). А вариант с одной очередью: push — O(N), pop — O(1).
Ёмкостная сложность: O(1) — нам нужно заранее определённое количество места в памяти.