Stacks & Queues · 21 of 38
Implement Stack using Deque
Build a pile of plates out of a tube that opens at both ends, by only ever using one of the ends.
- Simple way
- time O(n) push
room O(n) - Clever way
- time O(1) per operation
room O(n)
The problem, simply
A stack is a pile: the last thing you put on is the first thing you take off. You must build one with four moves: push(x) puts x on top, pop() takes the top off and gives it back, top() tells you the top without taking it, and empty() says if the pile is empty.
The only tool you may use inside is a deque. A deque is a tube with a door at each end. You can put things in or take things out at the front door or the back door, and each of those moves is fast.
A story to remember it
Picture it
The deque holds 1, 2, 3 from front to back. If we only ever use the back door, the back is the top of a stack.
- looking now
Examples
A normal run
- In
push 1, push 2, push 3, top, pop, pop, empty- Out
top = 3, pop = 3, pop = 2, empty = false
The last ball in, 3, comes out first. Then 2. The 1 is still inside, so the stack is not empty.
Push after pop
- In
push 5, pop, push 7, push 8, top- Out
pop = 5, top = 8
After 5 leaves, the tube is empty. 7 then 8 go in, and 8 is on top.
Brand new stack
- In
empty- Out
true
Nothing was ever pushed, so the stack is empty. Calling pop() here would be a mistake, so check empty() first.
The same number many times
- In
push 4, push 4, pop, top- Out
pop = 4, top = 4
A stack does not care about repeats. Two 4s go in, one comes out, one is still on top.
Way 1: the simple way
The first idea many people have is a plain list where the top lives at position 0, the front of the list. It feels natural to read the top first.
push(x): insertxat position 0. Every other item must slide one step right to make room.pop(): remove the item at position 0. Every other item slides one step left.top(): read position 0.empty(): check if the list size is 0.
Adding 4 at the start of a plain list means sliding 1, 2 and 3 one step to the right first.
- checking
- just added
import java.util.*;
class MyStack {
private final List<Integer> list = new ArrayList<>();
public void push(int x) {
list.add(0, x); // everybody slides right to make room
}
public int pop() {
if (list.isEmpty()) throw new RuntimeException("stack is empty");
return list.remove(0); // everybody slides left to fill the gap
}
public int top() {
if (list.isEmpty()) throw new RuntimeException("stack is empty");
return list.get(0);
}
public boolean empty() {
return list.isEmpty();
}
}How long it takes: push and pop slide every item, so each one costs O(n) when the stack holds n items. top and empty are O(1). Extra room: the list holds the items, O(n). It works, but it is slow for a big pile.
Way 2: the clever way
Use the deque and pick one door. We pick the back door. The back of the deque is the top of the stack.
push(x):addLast(x), put it in at the back.pop():removeLast(), take it out from the back.top():peekLast(), look at the back.empty():isEmpty().
Nothing ever slides, because a deque can open either door in one step.
Every stack move uses only the back door of the deque
01 / 09
Start with an empty deque. We decide to use only its back door for everything.
- looking now
- done
- just added
import java.util.*;
class MyStack {
private final Deque<Integer> dq = new ArrayDeque<>();
public void push(int x) {
dq.addLast(x); // in through the back door
}
public int pop() {
if (dq.isEmpty()) throw new RuntimeException("stack is empty");
return dq.removeLast(); // out through the same door
}
public int top() {
if (dq.isEmpty()) throw new RuntimeException("stack is empty");
return dq.peekLast(); // look, do not take
}
public boolean empty() {
return dq.isEmpty();
}
public int size() {
return dq.size();
}
}How long it takes: each move touches one door once, so every operation is O(1). Extra room: the deque holds the n items, O(n). Using the front door for everything (addFirst, removeFirst, peekFirst) works just as well. What matters is that push and pop use the same door.
Watch out for
- Popping or peeking an empty stack.
removeLast()throws andpeekLast()returnsnull, which crashes when unboxed toint. Checkempty()first or throw a clear error. ArrayDequedoes not allownullitems. If your stack must holdnull, useLinkedListas the deque.- Java's old
Stackclass works but is slow because every method is locked for threads. Interviewers like to hear thatArrayDequeis the modern choice.
What interviewers ask next
- Can a deque also act as a queue? Yes. Put things in at the back and take them out at the front. One deque can be a stack or a queue depending on which doors you use.
- How would you build the deque itself? Use a circular array with a
headindex and atailindex that wrap around, and double the array when it is full. Each move staysO(1)on average. - Add
getMin()inO(1). Keep a second deque that stores the smallest value so far next to every push, and pop it together with the main one. - What is the cost when the array inside the deque grows? A single push can cost
O(n)while copying, but doubling makes it rare, so the average staysO(1). This is called amortized time.
Remember this
Grown-up words: the pile is a stack (last in, first out), the tube with two doors is a deque (double ended queue), the back door is addLast and removeLast, and "fast on average even with rare big copies" is amortized O(1).