Chapter 12: Stacks and Stack-Based Algorithms
12.06Beginner / Intermediate5 topics16 min5 interactive animations

Special stacks

Special stack designs: a min stack and a max stack with O(1) extremum queries, two stacks in one array, a stack built from queues and sorting a stack with a second stack.

Learning goals

  • Track the minimum or maximum in O(1) with a parallel stack
  • Share one array between two stacks
  • Build a stack from a queue
  • Sort a stack using one extra stack

Prerequisites

ImplementationsQueues basics
12.06.1

Min stack

A min stack supports push, pop, top and getMin in O(1). Keep a second stack where each entry is the minimum of everything at or below that position: on push store min(x, previous min), on pop remove from both. The minimum is always the top of the second stack, and popping restores the previous minimum instantly. The price is O(n) extra space.

Push and pop values and read the minimum after every step.

Java
1class MinStack {
2 private final Deque<Integer> data = new ArrayDeque<>(), mins = new ArrayDeque<>();
3 void push(int x) { data.push(x); mins.push(mins.isEmpty() ? x : Math.min(x, mins.peek())); }
4 int pop() { mins.pop(); return data.pop(); }
5 int top() { return data.peek(); }
6 int getMin() { return mins.peek(); }
7}

Min stack / max stack in O(1)

Interactive · edit the input

Your input

Try

data stack (top is last)

empty

min stack: min of everything at or below

empty
Setup

Each push also records the minimum of the stack so far on a parallel stack, so the current minimum is always the top of that stack.

Step 1 / 10
Java
1class MinStack {
2 private final Deque<Integer> data = new ArrayDeque<>(), mins = new ArrayDeque<>();
3 void push(int x) { data.push(x); mins.push(mins.isEmpty() ? x : Math.min(x, mins.peek())); } // mins.peek() = min so far
4 int pop() { mins.pop(); return data.pop(); } // both stacks move together
5 int getMin() { return mins.peek(); } // O(1): no scan
6}
7// a max stack is identical with Math.max

Live state

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L2Each push also records the minimum of the stack so far on a parallel stack, so the current minimum is always the top of that stack.
2L3op=push(5) getMin=5push(5): min(5, previous min none) = 5 is pushed on the second stack.
3L3op=push(3) getMin=3push(3): min(3, previous min 5) = 3 is pushed on the second stack.
4L3op=push(7) getMin=3push(7): min(7, previous min 3) = 3 is pushed on the second stack.
5L3op=push(3) getMin=3push(3): min(3, previous min 3) = 3 is pushed on the second stack.
6L4op=pop() returned=3 getMin=3pop() returns 3 and pops the second stack too, which restores the previous minimum 3 instantly.
7L4op=pop() returned=7 getMin=3pop() returns 7 and pops the second stack too, which restores the previous minimum 3 instantly.
8L3op=push(2) getMin=2push(2): min(2, previous min 3) = 2 is pushed on the second stack.
9L4op=pop() returned=2 getMin=3pop() returns 2 and pops the second stack too, which restores the previous minimum 3 instantly.
10L5errors=0 return=3Final minimum: 3. Every operation is O(1); the price is a second stack of the same size.
12.06.2

Max stack

A max stack is identical with Math.max: the second stack stores the maximum so far. Supporting both min and max needs two auxiliary stacks. If you also need popMax() (removing the maximum wherever it is), you need a more elaborate structure such as a doubly linked list plus a TreeMap.

Switch the animation to track the maximum.

Java
1class MaxStack {
2 private final Deque<Integer> data = new ArrayDeque<>(), maxs = new ArrayDeque<>();
3 void push(int x) { data.push(x); maxs.push(maxs.isEmpty() ? x : Math.max(x, maxs.peek())); }
4 int pop() { maxs.pop(); return data.pop(); }
5 int getMax() { return maxs.peek(); }
6}

Min stack / max stack in O(1)

Interactive · edit the input

Your input

Try

data stack (top is last)

empty

min stack: min of everything at or below

empty
Setup

Each push also records the minimum of the stack so far on a parallel stack, so the current minimum is always the top of that stack.

Step 1 / 10
Java
1class MinStack {
2 private final Deque<Integer> data = new ArrayDeque<>(), mins = new ArrayDeque<>();
3 void push(int x) { data.push(x); mins.push(mins.isEmpty() ? x : Math.min(x, mins.peek())); } // mins.peek() = min so far
4 int pop() { mins.pop(); return data.pop(); } // both stacks move together
5 int getMin() { return mins.peek(); } // O(1): no scan
6}
7// a max stack is identical with Math.max

Live state

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L2Each push also records the minimum of the stack so far on a parallel stack, so the current minimum is always the top of that stack.
2L3op=push(5) getMin=5push(5): min(5, previous min none) = 5 is pushed on the second stack.
3L3op=push(3) getMin=3push(3): min(3, previous min 5) = 3 is pushed on the second stack.
4L3op=push(7) getMin=3push(7): min(7, previous min 3) = 3 is pushed on the second stack.
5L3op=push(3) getMin=3push(3): min(3, previous min 3) = 3 is pushed on the second stack.
6L4op=pop() returned=3 getMin=3pop() returns 3 and pops the second stack too, which restores the previous minimum 3 instantly.
7L4op=pop() returned=7 getMin=3pop() returns 7 and pops the second stack too, which restores the previous minimum 3 instantly.
8L3op=push(2) getMin=2push(2): min(2, previous min 3) = 2 is pushed on the second stack.
9L4op=pop() returned=2 getMin=3pop() returns 2 and pops the second stack too, which restores the previous minimum 3 instantly.
10L5errors=0 return=3Final minimum: 3. Every operation is O(1); the price is a second stack of the same size.
12.06.3

Two stacks in one array

To make two stacks share one array of size n, let stack A grow from the left end (index 0 upward) and stack B from the right end (index n - 1 downward). The array is only full when the two tops meet, so neither stack is limited to a fixed half and space is used efficiently. Push and pop are O(1).

Fill the array from both ends in the animation and try the full-array preset.

Java
1class TwoStacks {
2 private final int[] data; private int topA = -1, topB;
3 TwoStacks(int n) { data = new int[n]; topB = n; }
4 void pushA(int x) { if (topA + 1 == topB) throw new IllegalStateException("full"); data[++topA] = x; }
5 void pushB(int x) { if (topA + 1 == topB) throw new IllegalStateException("full"); data[--topB] = x; }
6 int popA() { if (topA < 0) throw new NoSuchElementException(); return data[topA--]; }
7 int popB() { if (topB == data.length) throw new NoSuchElementException(); return data[topB++]; }
8}

Two stacks sharing one array

Interactive · edit the input

Your input

Try

data[] · topA = -1 · topB = 5

0
1
2
3
4
Setup

One array of 5: stack A (blue) grows from the left, stack B (green) from the right. The array is only full when they meet, so neither stack wastes a fixed half.

Step 1 / 10
Java
1class TwoStacks {
2 private final int[] data; private int topA = -1, topB; // A grows right from index 0, B grows left from the end
3 TwoStacks(int n) { data = new int[n]; topB = n; }
4 void pushA(int x) { if (topA + 1 == topB) throw new IllegalStateException("full"); data[++topA] = x; }
5 void pushB(int x) { if (topA + 1 == topB) throw new IllegalStateException("full"); data[--topB] = x; }
6 int popA() { return data[topA--]; }
7 int popB() { return data[topB++]; }
8}

Live state

topA

-1

topB

5

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L3topA=-1 topB=5One array of 5: stack A (blue) grows from the left, stack B (green) from the right. The array is only full when they meet, so neither stack wastes a fixed half.
2L4op=pushA(1) topA=0 topB=5pushA(1): stored at index 0.
3L4op=pushA(2) topA=1 topB=5pushA(2): stored at index 1.
4L5op=pushB(1) topA=1 topB=4pushB(1): stored at index 4.
5L5op=pushB(2) topA=1 topB=3pushB(2): stored at index 3.
6L4op=pushA(3) topA=2 topB=3pushA(3): stored at index 2.
7L6op=popA() returned=3 topA=1popA() returns 3.
8L7op=popB() returned=2 topB=4popB() returns 2.
9L5op=pushB(3) topA=1 topB=3pushB(3): stored at index 3.
10L8return=A=[1, 2] B=[1, 3] errors=0Stack A (bottom to top) [1, 2], stack B [1, 3]. Space is shared dynamically, so overflow happens only when the total reaches 5.
12.06.4

Stack using queues

A queue is first-in first-out and a stack is last-in first-out, so one of the two operations must do extra work. With a single queue make push expensive: enqueue the new element and then rotate the older elements behind it (poll and offer size - 1 times), so the newest element is at the front. Pop and top are then O(1) and push is O(n). The alternative keeps push O(1) and rotates on pop.

Push values in the animation and watch the rotations.

Java
1class StackUsingQueue {
2 private final Queue<Integer> q = new ArrayDeque<>();
3 void push(int x) {
4 q.offer(x);
5 for (int i = 0; i < q.size() - 1; i++) q.offer(q.poll());
6 }
7 int pop() { return q.poll(); }
8 int top() { return q.peek(); }
9 boolean isEmpty() { return q.isEmpty(); }
10}

Implementing a stack with one queue

Interactive · edit the input

Your input

Try

queue (front = stack top)

empty
Setup

A queue is first-in first-out, a stack is last-in first-out. Keeping the newest element at the queue's front makes pop and top trivial; push does the work.

Step 1 / 17
Java
1class StackUsingQueue {
2 private final Queue<Integer> q = new ArrayDeque<>(); // the front of the queue is the top of the stack
3 void push(int x) {
4 q.offer(x);
5 for (int i = 0; i < q.size() - 1; i++) q.offer(q.poll()); // rotate: the new element moves to the front
6 }
7 int pop() { return q.poll(); } // O(1)
8 int top() { return q.peek(); }
9}

Live state

No condition evaluated on this step
Execution trace (dry run table)Show 17 steps
StepLineStateWhat happens
1L2A queue is first-in first-out, a stack is last-in first-out. Keeping the newest element at the queue's front makes pop and top trivial; push does the work.
2L4op=push(1) size=1offer(1): it joins the back of the queue, which is the wrong end for a stack.
3L5top=11 is now at the front: it is the stack's top. push cost 0 rotations: O(n).
4L4op=push(2) size=2offer(2): it joins the back of the queue, which is the wrong end for a stack.
5L5rotation=1 moves=1Rotate: poll the front and offer it to the back (1 of 1).
6L5top=22 is now at the front: it is the stack's top. push cost 1 rotation: O(n).
7L4op=push(3) size=3offer(3): it joins the back of the queue, which is the wrong end for a stack.
8L5rotation=1 moves=2Rotate: poll the front and offer it to the back (1 of 2).
9L5rotation=2 moves=3Rotate: poll the front and offer it to the back (2 of 2).
10L5top=33 is now at the front: it is the stack's top. push cost 2 rotations: O(n).
11L7op=pop() returned=3poll() returns 3, the most recently pushed value still in the stack: O(1).
12L4op=push(4) size=3offer(4): it joins the back of the queue, which is the wrong end for a stack.
13L5rotation=1 moves=4Rotate: poll the front and offer it to the back (1 of 2).
14L5rotation=2 moves=5Rotate: poll the front and offer it to the back (2 of 2).
15L5top=44 is now at the front: it is the stack's top. push cost 2 rotations: O(n).
16L7op=pop() returned=4poll() returns 4, the most recently pushed value still in the stack: O(1).
17L9moves=5 return=[2, 1]Stack (top first): [2, 1]; 5 queue rotations in total. Either push or pop must cost O(n) when only queues are available.
12.06.5

Sorted stack

Sorting a stack with only stack operations and one extra stack: pop each element x from the input, move every larger element from the temporary stack back to the input, push x onto the temporary stack, and repeat. The temporary stack always stays sorted, with its largest element on top. This is insertion sort on stacks: O(n²) time, O(n) extra space.

Try the already-sorted preset, which causes the most moves.

Java
1static Deque<Integer> sortStack(Deque<Integer> input) {
2 Deque<Integer> tmp = new ArrayDeque<>();
3 while (!input.isEmpty()) {
4 int x = input.pop();
5 while (!tmp.isEmpty() && tmp.peek() > x) input.push(tmp.pop());
6 tmp.push(x);
7 }
8 return tmp;
9}

Sorting a stack with one extra stack

Interactive · edit the input

Your input

Try

input stack (top is last)

340
31
312
983
924
235

tmp stack (kept sorted, largest on top)

empty
Setup

Pop each value x from the input and insert it into its sorted place in tmp, temporarily moving larger values back onto the input.

Step 1 / 38
Java
1static Deque<Integer> sortStack(Deque<Integer> input) { // result: largest value on top
2 Deque<Integer> tmp = new ArrayDeque<>();
3 while (!input.isEmpty()) {
4 int x = input.pop();
5 while (!tmp.isEmpty() && tmp.peek() > x) input.push(tmp.pop()); // make room: send bigger values back
6 tmp.push(x);
7 }
8 return tmp;
9}

Live state

No condition evaluated on this step
Execution trace (dry run table)Show 38 steps
StepLineStateWhat happens
1L2Pop each value x from the input and insert it into its sorted place in tmp, temporarily moving larger values back onto the input.
2L4x=23Pop x = 23 from the input.
3L6x=23 moves=0Push 23 onto tmp: everything below it is smaller or equal.
4L4x=92Pop x = 92 from the input.
5L6x=92 moves=0Push 92 onto tmp: everything below it is smaller or equal.
6L4x=98Pop x = 98 from the input.
7L6x=98 moves=0Push 98 onto tmp: everything below it is smaller or equal.
8L4x=31Pop x = 31 from the input.
9L5x=31 moved=98 moves=198 > 31: move 98 back to the input to make room.
10L5x=31 moved=92 moves=292 > 31: move 92 back to the input to make room.
11L6x=31 moves=2Push 31 onto tmp: everything below it is smaller or equal.
12L4x=92Pop x = 92 from the input.
13L6x=92 moves=2Push 92 onto tmp: everything below it is smaller or equal.
14L4x=98Pop x = 98 from the input.
15L6x=98 moves=2Push 98 onto tmp: everything below it is smaller or equal.
16L4x=3Pop x = 3 from the input.
17L5x=3 moved=98 moves=398 > 3: move 98 back to the input to make room.
18L5x=3 moved=92 moves=492 > 3: move 92 back to the input to make room.
19L5x=3 moved=31 moves=531 > 3: move 31 back to the input to make room.
20L5x=3 moved=23 moves=623 > 3: move 23 back to the input to make room.
21L6x=3 moves=6Push 3 onto tmp: everything below it is smaller or equal.
22L4x=23Pop x = 23 from the input.
23L6x=23 moves=6Push 23 onto tmp: everything below it is smaller or equal.
24L4x=31Pop x = 31 from the input.
25L6x=31 moves=6Push 31 onto tmp: everything below it is smaller or equal.
26L4x=92Pop x = 92 from the input.
27L6x=92 moves=6Push 92 onto tmp: everything below it is smaller or equal.
28L4x=98Pop x = 98 from the input.
29L6x=98 moves=6Push 98 onto tmp: everything below it is smaller or equal.
30L4x=34Pop x = 34 from the input.
31L5x=34 moved=98 moves=798 > 34: move 98 back to the input to make room.
32L5x=34 moved=92 moves=892 > 34: move 92 back to the input to make room.
33L6x=34 moves=8Push 34 onto tmp: everything below it is smaller or equal.
34L4x=92Pop x = 92 from the input.
35L6x=92 moves=8Push 92 onto tmp: everything below it is smaller or equal.
36L4x=98Pop x = 98 from the input.
37L6x=98 moves=8Push 98 onto tmp: everything below it is smaller or equal.
38L8moves=8 return=[3, 23, 31, 34, 92, 98]Sorted (bottom to top) [3, 23, 31, 34, 92, 98] with 8 extra moves. O(n²) time in the worst case, O(n) extra space.

Practice ladder

Now try it yourself

Easy

  • Show the min stack after pushing 5, 3, 7 and popping once
  • How do two stacks share an array without overlapping?

Medium

  • Implement a stack with getMin using a single stack that stores pairs (value, min so far)
  • Implement a queue using two stacks

Advanced

  • Implement a stack with O(1) push, pop, top, getMin and getMax using O(1) extra space per operation

Key takeaways

  • A parallel stack of running minima gives O(1) getMin.
  • Two stacks growing from opposite ends use the whole array.
  • Stack from queues costs O(n) on push or pop; sorting a stack is insertion sort.