Stacks & Queues · 15 of 38
Sort a Stack using recursion
Sort a pile of numbered blocks so the biggest is on top, using only the pile and helpers who can each hold one block.
- Simple way
- time O(n log n)
room O(n) - Clever way
- time O(n^2)
room O(n)
The problem, simply
You get a stack of numbers. Sort it so the smallest number is at the bottom and the biggest is on top. You may only use stack moves (push, pop, peek, isEmpty) and recursion. No loops over other arrays, no second stack, no list.
Some versions ask for the smallest on top instead. The method is the same; you only flip one comparison.
A story to remember it
Picture it
Before: mixed up. After: smallest at the bottom and biggest on top, using only the stack and recursion.
- looking now
- answer
Examples
Six numbers
- In
bottom [34, 3, 31, 98, 92, 23] top- Out
bottom [3, 23, 31, 34, 92, 98] top
98 is the biggest, so it ends on top.
The animation's stack
- In
bottom [3, 1, 4, 2] top- Out
bottom [1, 2, 3, 4] top
Every number slides down until the number below it is not bigger.
Upside down already
- In
bottom [3, 2, 1] top- Out
bottom [1, 2, 3] top
The worst case: every insert has to lift everything that is already sorted.
Repeats and negatives
- In
bottom [5, -2, 5, 0] top- Out
bottom [-2, 0, 5, 5] top
Equal numbers sit next to each other. Negative numbers sort like any others.
One number or none
- In
bottom [7] top and an empty stack- Out
bottom [7] top and an empty stack
Already sorted.
Way 1: the simple way
If you were allowed a list, you would pour the stack into it, sort the list, and pour it back.
- Pop everything into a list.
- Sort the list from small to big.
- Push the list from the smallest to the biggest. The biggest is pushed last, so it ends on top.
Way 1 pours the stack into a list, sorts the list, and pushes it back from smallest to biggest.
- looking now
import java.util.*;
class Solution {
public void sort(Deque<Integer> stack) {
List<Integer> all = new ArrayList<>();
while (!stack.isEmpty()) all.add(stack.pop());
Collections.sort(all); // small to big
for (int x : all) stack.push(x); // the biggest is pushed last, so it is on top
}
}How long it takes: sorting is O(n log n). Extra room: the list, O(n). It is faster, but it breaks the rule about other structures.
Way 2: the clever way
Two recursive functions, just like reversing a stack.
sortedInsert(x) puts x into an already sorted stack:
- If the stack is empty, or the top is not bigger than
x, pushx. Done. - Otherwise pop the top and hold it, call
sortedInsert(x)again, then push the held number back.
sort() sorts the whole stack:
- If the stack is empty, stop.
- Pop the top and hold it, call
sort()on the rest, thensortedInsert(held).
Pop everything, then drop each number into its place
01 / 13
The stack holds 3, 1, 4, 2 from the bottom, with 2 on top. Goal: smallest at the bottom, biggest on top.
- looking now
- checking
- done
- answer
- path
- just added
import java.util.*;
class Solution {
public void sort(Deque<Integer> stack) {
if (stack.isEmpty()) return;
int top = stack.pop(); // hold one number
sort(stack); // sort the rest
sortedInsert(stack, top); // drop the held number into its place
}
private void sortedInsert(Deque<Integer> stack, int x) {
if (stack.isEmpty() || stack.peek() <= x) {
stack.push(x); // the top is not bigger: x belongs here
return;
}
int held = stack.pop(); // a bigger number is in the way, lift it
sortedInsert(stack, x);
stack.push(held); // put it back on top
}
}How long it takes: there are n inserts and each one may lift every number already sorted, so the total is up to 0 + 1 + ... + (n - 1), which is O(n^2). This is insertion sort, done with the call stack. Extra room: at most about n waiting calls, O(n).
For smallest on top, change stack.peek() <= x to stack.peek() >= x.
Another way: one helper stack, no recursion
A common follow up: sort with one extra stack and loops. Keep the helper sorted with the smallest on top. Pop a number x from the input. While the helper's top is smaller than x, move those numbers back to the input. Then push x onto the helper. At the end, pour the helper back: the smallest goes in first, so the biggest ends on top.
import java.util.*;
class Solution {
public void sort(Deque<Integer> stack) {
Deque<Integer> helper = new ArrayDeque<>(); // kept with the smallest on top
while (!stack.isEmpty()) {
int x = stack.pop();
while (!helper.isEmpty() && helper.peek() < x) {
stack.push(helper.pop()); // smaller ones step back into the input
}
helper.push(x);
}
while (!helper.isEmpty()) stack.push(helper.pop()); // smallest in first, biggest ends on top
}
}Numbers that step back into the input get popped again later, so this is also O(n^2) time in the worst case, with O(n) room for the helper.
Watch out for
- Using
<instead of<=insortedInsert. It still sorts, but equal numbers get lifted for no reason. - Forgetting the empty check before
peek().ArrayDeque.peek()returnsnullon an empty stack, and comparingnullwith anintthrows. - Deep recursion. A stack of 100,000 numbers means 100,000 waiting calls and a likely
StackOverflowError.
What interviewers ask next
- Why is it
O(n^2)and notO(n)? Each insert can lift all the numbers already sorted, like insertion sort on a reversed list. - Reverse a stack with recursion. Same shape, but insert at the bottom instead of in sorted order.
- Sort a stack with no recursion and only one extra stack. That is the "Another way" above.
- Can a stack be sorted faster? Any sort that only compares pairs needs
O(n log n)comparisons. With two helper stacks you can do a merge sort inO(n log n); with only the call stack or one helper, the simple methods above areO(n^2).
Remember this
Grown-up words: the tower is a stack, the helpers are frames on the call stack, sortedInsert is the insert step of insertion sort, and "no other structure" means no auxiliary data structure.