Topics › Recursion and Backtracking › Get a Strong Hold
Sort a stack using recursion
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given a stack of integers. Sort it so that the largest element is on top and the smallest at the bottom, using only the stack operations push, pop, peek and isEmpty, and recursion. No loops, no arrays, no second stack: the only extra storage allowed is the function call stack itself.
Constraints: up to a few thousand elements; values fit in an int.
In Java the stack is a Deque<Integer> backed by ArrayDeque, whose push, pop and peek all act on the top. The legacy java.util.Stack class would work the same way but is not recommended for new code. Examples below list the stack from bottom to top, so the last value written is the one on top.
Example 1
stack (bottom to top) = [3, 1, 2]
answer = [1, 2, 3] // 3 is on top
Example 2
stack (bottom to top) = [11, 2, 32, 3, 41]
answer = [2, 3, 11, 32, 41]
Two methods in the Java file break the “stack operations only” rule on purpose: they are the baselines the recursive solution is checked against.
Approach 1: Brute force, copy out and sort
Intuition. Treat the stack as a bag of numbers. Pop everything into a list, sort the list, and push the values back from smallest to largest so that the largest ends on top.
Algorithm.
- Pop every element into an
ArrayList. - Sort the list in ascending order.
- Push the elements back in that order.
static void sortViaList(Deque<Integer> stack) {
List<Integer> items = new ArrayList<>();
while (!stack.isEmpty()) items.add(stack.pop());
Collections.sort(items); // ascending
for (int v : items) stack.push(v); // smallest pushed first, so the largest ends on top
}
Complexity. Time O(n log n) for the sort. Space O(n) for the list.
This is the fastest way to sort a stack and what you would write in production. The exercise forbids it because its purpose is to practise recursion.
Approach 2: Insertion sort with a helper stack
Intuition. Insertion sort maintains a sorted prefix and inserts one new element at a time. With stacks, the “sorted prefix” is a second stack helper whose smallest element is on top. To insert v, first move every element of helper that is smaller than v back onto the input stack (they will be re-inserted later), push v, and continue. When the input is empty, pour helper back: its smallest element comes out first and is pushed first, so the largest finishes on top.
Algorithm.
- While the input stack is not empty, pop
v. - While
helperis not empty andhelper.peek() < v, pop fromhelperand push onto the input. - Push
vontohelper. - When the input is empty, pop everything from
helperand push it onto the input.
static void sortWithHelperStack(Deque<Integer> stack) {
Deque<Integer> helper = new ArrayDeque<>(); // invariant: sorted, smallest on top
while (!stack.isEmpty()) {
int v = stack.pop();
while (!helper.isEmpty() && helper.peek() < v) stack.push(helper.pop()); // smaller ones wait on the input
helper.push(v);
}
while (!helper.isEmpty()) stack.push(helper.pop()); // pour back: smallest first, largest on top
}
Dry run on [3, 1, 2] (bottom to top), writing stacks bottom to top:
| step | input | helper | note |
|---|---|---|---|
| pop 2 | [3, 1] |
[2] |
helper empty, push |
| pop 1 | [3] |
[2, 1] |
2 is not smaller than 1 |
| pop 3 | [1, 2] |
[3] |
1 and 2 are smaller than 3, moved back |
| pop 2 | [1] |
[3, 2] |
|
| pop 1 | [] |
[3, 2, 1] |
|
| pour back | [1, 2, 3] |
[] |
1 pushed first, 3 last |
Complexity. Time O(n^2) in the worst case, because an element can be moved back and forth many times. Space O(n) for the helper stack.
This approach is the bridge to the recursive one: the recursion below does exactly the same work, but the helper stack is replaced by the call stack.
Approach 3: Optimal, recursion only
Intuition. Recursion gives us a place to park one element per call: the local variable of a stack frame. “Sort this stack” becomes: pop the top, trust the recursion to sort the remaining n - 1 elements, then put the popped value back in its correct place. That second step is itself recursive: “insert v into a sorted stack” means pop elements larger than v one at a time (each parked in its own frame), push v when the top is no longer larger, then push the parked elements back on the way up.
Algorithm.
sortRecursive(stack):
- If the stack is empty, return.
top = pop().sortRecursive(stack).insertSorted(stack, top).
insertSorted(stack, v):
- If the stack is empty or
peek() <= v, pushvand return. top = pop().insertSorted(stack, v).push(top).
static void sortRecursive(Deque<Integer> stack) {
if (stack.isEmpty()) return; // base case: nothing to sort
int top = stack.pop();
sortRecursive(stack); // the remaining n - 1 elements, sorted
insertSorted(stack, top);
}
static void insertSorted(Deque<Integer> stack, int v) {
if (stack.isEmpty() || stack.peek() <= v) { // base case: v belongs on top
stack.push(v);
return;
}
int top = stack.pop(); // top > v: lift it off
insertSorted(stack, v); // place v below it
stack.push(top); // and put the larger one back
}
Dry run on [3, 1, 2] (bottom to top). Each sortRecursive frame pops one value, then the frames unwind from the smallest problem:
| frame | holds | stack before insert | after insertSorted |
|---|---|---|---|
| sort #3 (deepest) | 3 | [] |
[3] |
| sort #2 | 1 | [3] |
pop 3 (3 > 1), push 1, push 3 → [1, 3] |
| sort #1 | 2 | [1, 3] |
pop 3 (3 > 2), top is 1 ≤ 2 so push 2, push 3 → [1, 2, 3] |
The order in which frames finish is the reverse of the order they started, so the element popped last (the original bottom, 3) is inserted first into an empty stack, and the original top (2) is inserted last.
Complexity. Time O(n^2): n insertions, each popping up to n elements. Space O(n) for the recursion, up to n frames of sortRecursive plus up to n frames of insertSorted at the deepest point.
“Optimal” here means optimal under the problem’s constraint, not the fastest possible: Approach 1 is faster, but it uses an auxiliary data structure. The recursive version uses nothing but the call stack, which is the skill the problem is teaching.
Edge cases and pitfalls
- Empty stack.
sortRecursivereturns immediately;insertSortedon an empty stack just pushes. - Single element. One pop, one recursive call on the empty stack, one push.
- Duplicates. The base case uses
peek() <= v, so an equal value is pushed on top of its twin rather than dug underneath it. Using<is also correct but does more work. - Negative numbers. Nothing changes; only comparisons are used.
- Guard order.
stack.isEmpty() || stack.peek() <= vrelies on short-circuit evaluation:peek()on an emptyArrayDequereturnsnull, and unboxingnullthrowsNullPointerException. Keep the emptiness check first. - Comparing
Integerobjects.helper.peek() < vunboxes correctly, but comparing twoIntegers with==compares references and fails outside the small-value cache. Use<,>,equalsorintValue(). - Recursion depth. Roughly
2nframes at the deepest point. A few thousand elements are fine on a default JVM stack; for millions use Approach 1. - Which end is “sorted”. Some versions of this problem want the smallest on top. Flip the comparison in
insertSortedtopeek() >= vand the same code sorts the other way.
Related problems
- Reverse a Stack uses the identical pop-recurse-reinsert shape, with “insert at the bottom” instead of “insert in sorted position”.
- Sort a Stack is the same task in the stacks section of the SDE sheet.
- Implement Stack using Arrays builds the data structure these operations run on.
- Implement Min Stack is another problem about keeping order information inside a stack.