Стек
Очередь на стеках
Реализовать очередь с помощью стеков
Постановка задачи
Описание (LeetCode - 232. Implement Queue using Stacks)
Нужно реализовать очередь, используя только стандартные операции стека.
Класс MyQueue должен поддерживать операции:
push(x)- добавить элементxв конец очереди.pop()- удалить элемент из начала очереди и вернуть его.peek()- вернуть элемент из начала очереди, не удаляя его.empty()- проверить, пуста ли очередь.
Можно использовать только операции стека: добавить на вершину, удалить с вершины, посмотреть вершину и проверить, пуст ли стек.
Пример:
MyQueue queue = new MyQueue()
queue.push(1)
queue.push(2)
queue.peek()
queue.pop()
queue.empty()
Результат:
1
1
false
Ограничения:
1 <= x <= 9- Будет выполнено не более
100вызовов операций. - Операции
popиpeekвызываются только для непустой очереди.
Решение
Очередь работает по правилу FIFO: первым добавили, первым достали. Стек работает наоборот: последним добавили, первым достали.
Нам нужны два стека. Один принимает новые элементы, второй отдает их в нужном порядке.
Идея решения:
inStack- сюда кладем элементы приpush.outStack- отсюда забираем элементы приpopиpeek.
При push(x) просто делаем push в inStack.
При pop или peek, если outStack пуст, перекладываем все элементы из
inStack в outStack. После такого переноса порядок разворачивается, и на
вершине outStack оказывается начало очереди.
Основные шаги:
push(x)добавляет элемент вinStack.- Перед
popилиpeek, еслиoutStackпуст, переносим все изinStackвoutStack. pop()удаляет вершинуoutStack.peek()возвращает вершинуoutStack.empty()проверяет, пусты ли оба стека.
Каждый элемент перекладывается между стеками не больше одного раза. Поэтому дорогая операция переноса амортизируется, и в среднем операции работают быстро.
Рассмотрим пример:
push(1): кладем элемент вinStack.
push(2): снова кладем вinStack. Начало очереди пока внизу.
peek():outStackпуст, поэтому переносим элементы изinвout.
- Теперь вершина
outStack- это начало очереди.peek()возвращает1.
pop()удаляет вершинуoutStack.
Задачи про стек на очередях и очередь на стеках - базовые. Они проверяют, что кандидат умеет работать с этими структурами и понимает, как они устроены.
Кроме того, очередь на двух стеках легко расширить до очереди с текущим
минимумом или максимумом: достаточно уметь реализовать Min стек или Max стек и
хранить такой стек вместо обычного в inStack и outStack.
Оценка сложности
Временная сложность
push работает за O(1). pop и peek работают за амортизированное O(1):
каждый элемент переносится из inStack в outStack только один раз. empty
работает за O(1).
Пространственная сложность
Мы храним все элементы в двух стеках, поэтому пространственная сложность равна
O(n).
Код решения
Приведем код решения.
type MyQueue struct {
inStack []int
outStack []int
}
func Constructor() MyQueue {
return MyQueue{
inStack: []int{},
outStack: []int{},
}
}
func (q *MyQueue) Push(x int) {
q.inStack = append(q.inStack, x)
}
func (q *MyQueue) move() {
for len(q.inStack) > 0 {
n := len(q.inStack)
top := q.inStack[n-1]
q.inStack = q.inStack[:n-1]
q.outStack = append(q.outStack, top)
}
}
func (q *MyQueue) Pop() int {
if len(q.outStack) == 0 {
q.move()
}
n := len(q.outStack)
top := q.outStack[n-1]
q.outStack = q.outStack[:n-1]
return top
}
func (q *MyQueue) Peek() int {
if len(q.outStack) == 0 {
q.move()
}
return q.outStack[len(q.outStack)-1]
}
func (q *MyQueue) Empty() bool {
return len(q.inStack) == 0 && len(q.outStack) == 0
}Итоги
Задача Implement Queue using Stacks показывает, как с помощью двух стеков получить поведение очереди.
- Суть алгоритма:
pushидет вinStack, аpopиpeekработают черезoutStack. КогдаoutStackпуст, мы перекладываем элементы изinвoutи разворачиваем порядок. - Эффективность:
- Временная сложность:
pushиemptyработают заO(1),popиpeek- за амортизированноеO(1). - Пространственная сложность:
O(n), так как мы храним элементы в двух стеках.
- Временная сложность:
- Граничные случаи:
- Пустая очередь:
empty()возвращаетtrue. outStackеще не пуст: новые элементы остаются вinStack, пока не закончатся элементы вoutStack.
- Пустая очередь:
- Оптимальное решение: мы делаем
pushбыстрым, а стоимость переноса распределяем между последующимиpopиpeek. Это удобный вариант, когда мы часто выполняем операции чтения и удаления из начала очереди.