Chapter 7: Stacks

Data Structures
LIFO and basic operationsArray-based stackLinked stack Bracket matchingNext greater elementTowers of Hanoi Stock spanInfix → postfix / prefixPostfix evaluation
Sercan KÜLCÜ · Giresun University · Computer Engineering

Introduction

Learning Outcomes

The LIFO model

Explain the last-in, first-out behavior of a stack.

Implementation

Implement a stack with an array and with a linked list.

Applications

Use stacks to parse expressions and manage method calls.

Cost analysis

Analyze the time and space cost of the basic stack operations.

The Stack ADT

The Stack

  • Items are added and removed at one end only: the top.
  • Think of a pile of plates or boxes.
  • To reach the bottom item, you must remove everything above it, one at a time.
  • The rule is LIFO (last in, first out), or equivalently FILO (first in, last out).

A new plate goes on top, and you take plates from the top.

UndoThe most recent action is undone first.
Browser “Back”Returns to the most recently visited page.
Function callsThe most recently called function finishes first.

The Stack ADT

Basic Operations

OperationDescription
push(x)Adds a new item x on top.
pop()Removes and returns the top item.
top()Returns the top item without removing it (Java: peek()).
isEmpty()Is the stack empty?
size()Returns the number of items.
Users see only the top item; there is no direct access to the items below it.

The Stack ADT

push and pop, Step by Step

stackemptyOperationReturnedsize

The stack starts empty: size = 0, nothing on top.

10stack← topOperationReturnedsizepush(10)—1

push(10): 10 goes onto the empty stack; the top is now 10.

1015stack← topOperationReturnedsizepush(10)—1push(15)—2

push(15): 15 goes on top of 10.

101520stack← topOperationReturnedsizepush(10)—1push(15)—2push(20)—3

push(20): 20 goes on top; size = 3.

101520stack← topOperationReturnedsizepush(10)—1push(15)—2push(20)—3top()203

top(): reads the top item, 20, but does not remove it.

1015stack← top20← poppedOperationReturnedsizepush(10)—1push(15)—2push(20)—3top()203pop()202

pop(): 20, the last in, is the first out (LIFO).

10stack← top15← poppedOperationReturnedsizepush(10)—1push(15)—2push(20)—3top()203pop()202pop()151

pop(): removes 15; the top is now 10.

stackempty10← poppedOperationReturnedsizepush(10)—1push(15)—2push(20)—3top()203pop()202pop()151pop()100

pop(): removes 10; the stack is empty again.

stackemptyunderflow!OperationReturnedsizepush(10)—1push(15)—2push(20)—3top()203pop()202pop()151pop()100pop()error!0

pop() on an empty stack → underflow error.

The Stack ADT

The Stack ADT as a Java Interface

public interface Stack<E> {
  int size();
  boolean isEmpty();
  void push(E e);
  E top();    // null if empty
  E pop();    // throws if empty
}
  • ADT = what? The interface states the operations and what they mean, not how the data is stored.
  • Implementation = how? ArrayStack<E> implements Stack<E>, or a linked-list class.
  • Code written against the interface lets you swap the implementation without touching the callers.
On an empty stackpop()top() / peek()
Our Stack<E> (above)throws an exceptionreturns null
java.util.StackEmptyStackExceptionEmptyStackException
java.util.ArrayDequeNoSuchElementExceptionreturns null

The same abstract operation handles “empty” differently from library to library, so check isEmpty() first. Our interface shares its name with java.util.Stack; don't import both in the same file.

The Stack ADT

Algorithms for the Operations (Array)

The stack lives in an array; top is the index of the top item, and an empty stack has top = −1.

push(x)
begin
  if stack is full   // top = cap−1
    error: overflow
  else
    increment top by 1
    stack[top] ← x
end
pop()
begin
  if stack is empty    // top = −1
    error: underflow
  else
    x ← stack[top]; decrement top by 1
    return x
end
top()
begin
  if stack is empty return null
  else return stack[top]
end
isEmpty() · size()
isEmpty: true if top = −1, else false
size:    return top + 1

The Stack ADT

Time and Space Complexity

OperationTimeWhy?
pushO(1)only the top changes
popO(1)only the top changes
topO(1)the top is known directly
isEmpty, sizeO(1)a counter / index is kept
searching for an itemO(n)items must be removed one by one
  • The basic operations take the same time no matter how many items the stack holds.
  • A stack of n items uses O(n) memory; the operations need only O(1) extra.
  • A dynamic array grows when it fills up, so push is amortized O(1).
A stack buys speed by restricting access: because of the “top only” rule, no basic operation ever shifts or scans the items.

Implementation

Kinds of Stacks

Fixed size (capacity 4)01012023034050push(50) → overflow errorDynamic size (doubles when full)010120230340fullnew array + copy1020304050
Fixed-Size Stack
  • Capacity is set up front; it can neither grow nor shrink.
  • Pushing onto a full stack → overflow.
  • Popping an empty stack → underflow.
  • Backed by a fixed-size array.
Dynamic-Size Stack
  • Grows and shrinks at run time.
  • Increases its capacity automatically when full (e.g., doubles it).
  • May shrink when it holds far fewer items than its capacity.
  • Implemented with a dynamic array or a linked list.

Popping an empty stack (underflow) is an error in both kinds; overflow happens only in a fixed-size stack.

Implementation

Ways to Implement a Stack

The stack ADT can be implemented with an array or a linked list.

10015120234top = 2array: push → top++ , pop → top−−201510nulltoplinked list: push/pop at the head
Array-Based push: increment top, then write the item to arr[top].
pop: read arr[top], then decrement top.
Linked-List-Based push: add a new node at the head of the list.
pop: remove the head node and return its value.

Implementation

Array-Based Stack

This is also called a bounded stack: its capacity is fixed when it is created and never changes.

05182334567datatop = 2unused capacity (null)size = top + 1 = 3

Advantages

  • Simple and easy to implement.
  • No per-node references → saves memory.
  • Memory use is tightly bounded, which makes it safer against attacks that try to exhaust memory.
  • Items are contiguous → cache-friendly.

Watch out for

  • Check that the stack is not full before pushing.
  • Too small a capacity overflows; too large wastes memory.
  • In an array of objects, set a popped cell to null; otherwise the object lingers in memory.

Implementation

Array-Based Stack: Java

public class ArrayStack<E> implements Stack<E> {
    private E[] data;
    private int top = -1;                    // -1 → empty stack
    public ArrayStack(int capacity) { data = (E[]) new Object[capacity]; }
    public boolean isEmpty() { return top == -1; }
    public int size()        { return top + 1; }
    public E top()           { return isEmpty() ? null : data[top]; }
    public void push(E x) {
        if (top == data.length - 1) throw new IllegalStateException("Overflow");
        data[++top] = x;
    }
    public E pop() {
        if (isEmpty()) throw new IllegalStateException("Underflow");
        E x = data[top];
        data[top--] = null;                  // drop the reference
        return x;
    }
}

Java cannot create a generic array directly, so we cast an Object[] (the compiler issues an “unchecked” warning). Every operation is O(1).

Implementation · Step through the code

Code Trace: Array-Based Stack

data (capacity 4)[0]null[1]null[2]null[3]nulltop = −1 (empty)calls in main()s.push(10)s.push(15)s.push(20)s.top()s.pop()s.push(25)s.push(30)s.push(40)

new ArrayStack<>(4): four cells, all null; an empty stack has top = −1.

data (capacity 4)[0]null[1]null[2]null[3]nulltop = −1 (empty)calls in main()s.push(10)s.push(15)s.push(20)s.top()s.pop()s.push(25)s.push(30)s.push(40)

push(10): full? top = -1, data.length − 1 = 3 → no, continue.

data (capacity 4)[0]10[1]null[2]null[3]null← top = 0calls in main()s.push(10)✓s.push(15)s.push(20)s.top()s.pop()s.push(25)s.push(30)s.push(40)

data[++top] = x: top goes -1 → 0 first, then data[0] = 10. One assignment → O(1).

data (capacity 4)[0]10[1]null[2]null[3]null← top = 0calls in main()s.push(10)✓s.push(15)s.push(20)s.top()s.pop()s.push(25)s.push(30)s.push(40)

push(15): full? top = 0, data.length − 1 = 3 → no, continue.

data (capacity 4)[0]10[1]15[2]null[3]null← top = 1calls in main()s.push(10)✓s.push(15)✓s.push(20)s.top()s.pop()s.push(25)s.push(30)s.push(40)

data[++top] = x: top goes 0 → 1 first, then data[1] = 15. One assignment → O(1).

data (capacity 4)[0]10[1]15[2]null[3]null← top = 1calls in main()s.push(10)✓s.push(15)✓s.push(20)s.top()s.pop()s.push(25)s.push(30)s.push(40)

push(20): full? top = 1, data.length − 1 = 3 → no, continue.

data (capacity 4)[0]10[1]15[2]20[3]null← top = 2calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()s.pop()s.push(25)s.push(30)s.push(40)

data[++top] = x: top goes 1 → 2 first, then data[2] = 20. One assignment → O(1).

data (capacity 4)[0]10[1]15[2]20[3]null← top = 2calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()→ 20s.pop()s.push(25)s.push(30)s.push(40)

top(): the stack is not empty → returns data[2] = 20. The array and top do not change.

data (capacity 4)[0]10[1]15[2]20[3]null← top = 2calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()→ 20s.pop()s.push(25)s.push(30)s.push(40)

pop(): isEmpty()? top = 2 ≠ −1 → not empty, continue.

data (capacity 4)[0]10[1]15[2]20[3]null← top = 2calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()→ 20s.pop()s.push(25)s.push(30)s.push(40)

E x = data[top] → x = 20 (save the top item).

data (capacity 4)[0]10[1]15[2]null[3]null← top = 1calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()→ 20s.pop()s.push(25)s.push(30)s.push(40)

data[top--] = null: clears the cell (the object is left to the garbage collector); top → 1.

data (capacity 4)[0]10[1]15[2]null[3]null← top = 1calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()→ 20s.pop()→ 20s.push(25)s.push(30)s.push(40)

return 20: last in, first out (LIFO).

data (capacity 4)[0]10[1]15[2]null[3]null← top = 1calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()→ 20s.pop()→ 20s.push(25)s.push(30)s.push(40)

push(25): full? top = 1, data.length − 1 = 3 → no, continue.

data (capacity 4)[0]10[1]15[2]25[3]null← top = 2calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()→ 20s.pop()→ 20s.push(25)✓s.push(30)s.push(40)

data[++top] = x: top goes 1 → 2 first, then data[2] = 25. One assignment → O(1).

data (capacity 4)[0]10[1]15[2]25[3]null← top = 2calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()→ 20s.pop()→ 20s.push(25)✓s.push(30)s.push(40)

push(30): full? top = 2, data.length − 1 = 3 → no, continue.

data (capacity 4)[0]10[1]15[2]25[3]30← top = 3calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()→ 20s.pop()→ 20s.push(25)✓s.push(30)✓s.push(40)

data[++top] = x: top goes 2 → 3 first, then data[3] = 30. One assignment → O(1).

data (capacity 4)[0]10[1]15[2]25[3]30← top = 3calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()→ 20s.pop()→ 20s.push(25)✓s.push(30)✓s.push(40)

push(40): full? top = 3, data.length − 1 = 3 → yes, full!

data (capacity 4)[0]10[1]15[2]25[3]30← top = 3calls in main()s.push(10)✓s.push(15)✓s.push(20)✓s.top()→ 20s.pop()→ 20s.push(25)✓s.push(30)✓s.push(40)✗ Overflow

The array is full: IllegalStateException("Overflow") is thrown. This is overflow in a fixed-capacity stack; a dynamic array would grow here instead.

public void push(E x) {
  if (top == data.length - 1)
    throw new IllegalStateException("Overflow");
  data[++top] = x;
}
public E pop() {
  if (isEmpty())
    throw new IllegalStateException("Underflow");
  E x = data[top];
  data[top--] = null;          // drop the reference
  return x;
}
public E top() { return isEmpty() ? null : data[top]; }

Implementation

Dynamic Array: Doubling When Full

public void push(E x) {
  if (top == data.length - 1)
    resize(2 * data.length);
  data[++top] = x;
}
private void resize(int newCap) {
  E[] bigger = (E[]) new Object[newCap];
  for (int i = 0; i <= top; i++)
    bigger[i] = data[i];
  data = bigger;
}
  • When the array is full, we allocate one twice as large and copy the items over, so a push that grows the array takes O(n).
  • Growth is rare: over n pushes the copies total 2 + 4 + 8 + … < 2n, and the total cost is < 3n → push is amortized O(1).
  • Adding a fixed number of cells (+c) each time gives O(n²) in total, O(n) per push; capacity must grow proportionally.

Amortized cost is an average over a long sequence of operations, not the cost of any single one.

Implementation

Linked-List Stack

  • Items live in a chain of linked nodes → the size is flexible.
  • The size changes at run time as needed; no overflow (until memory runs out).
  • Each node stores a reference (next), which costs extra memory.
  • No random access, but reaching the top item (the head of the list) is quick and simple.
  • Insertion and removal happen at the head, so both are O(1).
302010nulltoppush / pop onlyat the head (top) node
class Node {
    int data;
    Node next;
    Node(int data) { this.data = data; }   // next = null
}

Why insert at the head? Deleting from the tail of a singly linked list requires finding the node before the tail → O(n).

Implementation · Step through the code

Code Trace: push on a Linked List

top = nullnode = [data | next]

Start: top = null, size = 0 → empty stack.

10nulltop = nulltempnode = [data | next]

push(10): creates new Node(10); temp points to it, and its next field defaults to null.

10nulltop = nulltempnode = [data | next]

temp.next = top: top is null, so next stays null (the new node stands alone).

10nulltoptempnode = [data | next]

top = temp: the new node is now the top of the stack.

10nulltoptempnode = [data | next]

size++ → 1. The method returns; the local temp goes away, and the node is reached through top.

15null10nulltoptempnode = [data | next]

push(15): creates node 15. It is not linked to the list yet; top still points to 10.

1510nulltoptempnode = [data | next]

temp.next = top: 15 links to 10, the old top. We link first so the old list isn't lost.

1510nulltoptempnode = [data | next]

top = temp: the new top is 15. Insertion at the head, no shifting → O(1).

1510nulltoptempnode = [data | next]

size++ → 2.

1510nulltopnode = [data | next]

push returns. Stack (top to bottom): 15 → 10. The last item pushed is on top.

Node top;          // top of the stack
int size;

public void push(int data) {
  Node temp = new Node(data);
  temp.next = top;
  top = temp;
  size++;
}

Implementation · Step through the code

Code Trace: pop on a Linked List

1510nulltopnode = [data | next]

The stack holds 15 and 10. The loop will call pop() 3 times.

1510nulltopnode = [data | next]

pop(): check for empty first; top is not null → continue.

1510nulltopnode = [data | next]

result = top.data → save 15 (before the node is unlinked).

1510nulltopnode = [data | next]

top = top.next: top moves down one → node(10). Nothing refers to the old top node anymore.

1510nulltopnode = [data | next]

size-- → 1.

1510nulltopnode = [data | next]

return 15. The garbage collector reclaims the unreachable node.

10nulltopnode = [data | next]

main prints the returned value, 15.

10nulltopnode = [data | next]

pop(): check for empty first; top is not null → continue.

10nulltopnode = [data | next]

result = top.data → save 10 (before the node is unlinked).

10nulltop = nullnode = [data | next]

top = top.next: top moves down one → null. Nothing refers to the old top node anymore.

10nulltop = nullnode = [data | next]

size-- → 0.

10nulltop = nullnode = [data | next]

return 10. The garbage collector reclaims the unreachable node.

top = nullnode = [data | next]

main prints the returned value, 10.

top = nullnode = [data | next]

pop(): top == null → the stack is empty!

top = nullnode = [data | next]

An underflow exception stops the program. Without the check, top.data would throw a NullPointerException.

public int pop() {
  if (top == null)
    throw new RuntimeException("empty");
  int result = top.data;
  top = top.next;
  size--;
  return result;
}
// in main (stack: 15 → 10):
for (int k = 0; k < 3; k++)
  System.out.println(pop());

Implementation

Array or Linked List?

CriterionArray-basedLinked-list-based
Sizefixed capacity (a dynamic array grows)flexible, changes at run time
push / popO(1) (amortized O(1) with growth)O(1)
Overflowpossibleonly if memory runs out
Memory per itemdata onlydata + next reference
Wasted spaceunused capacitynone
JavaArrayDeque, Stack (Vector-based)LinkedList (as a Deque)
In new Java code, use Deque<Integer> s = new ArrayDeque<>(); with push/pop/peek; the legacy Stack class is synchronized and therefore slower.

Applications

Where Stacks Are Used

ExpressionsConverting and evaluating infix, prefix, and postfix; bracket matching.
Undo / redoUndo and redo in text editors.
Browser historyTwo stacks drive the “Back” and “Forward” buttons.
The call stackFunction calls, local variables, and return addresses.
BacktrackingMaze solving, n-queens, the knight's tour.
AlgorithmsTowers of Hanoi, tree traversal, stock span, histogram problems.
Graph algorithmsDepth-first search, topological sort, strongly connected components.
Reversing a stringCharacters go in one order and come out in reverse.
Next greater elementFor each item, the first larger value to its right.

Applications

Browser Back and Forward: Two Stacks

startemptyback stackemptyforward stackcurrent pageHome

The browser opens on Home; both stacks are empty.

visit("News")Homeback stackemptyforward stackcurrent pageNews

visit(News): push the old page onto the back stack and open the new one.

visit("Sports")HomeNewsback stackemptyforward stackcurrent pageSports

visit(Sports): push the old page onto the back stack and open the new one.

back()Homeback stackSportsforward stackcurrent pageNews

back(): push the current page onto the forward stack; pop the back stack → News.

back()emptyback stackSportsNewsforward stackcurrent pageHome

back(): push the current page onto the forward stack; pop the back stack → Home.

forward()Homeback stackSportsforward stackcurrent pageNews

forward(): push the current page onto the back stack; pop the forward stack → News.

visit("Weather")HomeNewsback stackemptyforward stackcurrent pageWeathercleared: Sports

visit(Weather): push the old page onto the back stack and open the new one. A new path begins, so the forward stack is cleared.

back()Homeback stackWeatherforward stackcurrent pageNews

back(): push the current page onto the forward stack; pop the back stack → News.

Rules: visit → push onto back, clear forward · back → push onto forward, pop back · forward → push onto back, pop forward. Each is O(1). Undo / redo in a text editor follows the same pattern.

Applications · Step through the code

The Call Stack: fact(3) Step by Step

main()call stackmain() is runningframe = parameters+ local variables+ return address

main is running and is about to call fact(3). Every call pushes a new stack frame.

main()fact(n=3)call stackpush: fact(3)stack depth = 1

The frame for fact(3) is pushed. n <= 1? 3 <= 1 → false.

main()fact(n=3)3 * fact(2) = ?call stackfact(3) suspendedfact(2) must finish first

3 * fact(2) can't be computed until fact(2) finishes. This frame waits, suspended.

main()fact(n=3)3 * fact(2) = ?fact(n=2)call stackpush: fact(2)stack depth = 2

The frame for fact(2) is pushed. n <= 1? 2 <= 1 → false.

main()fact(n=3)3 * fact(2) = ?fact(n=2)2 * fact(1) = ?call stackfact(2) suspendedfact(1) must finish first

2 * fact(1) can't be computed until fact(1) finishes. This frame waits, suspended.

main()fact(n=3)3 * fact(2) = ?fact(n=2)2 * fact(1) = ?fact(n=1)call stackpush: fact(1)stack depth = 3

The frame for fact(1) is pushed. n <= 1? 1 <= 1 → true.

main()fact(n=3)3 * fact(2) = ?fact(n=2)2 * fact(1) = ?fact(n=1)call stackbase casereturn 1

Base case: return 1. Now the stack starts unwinding.

main()fact(n=3)3 * fact(2) = ?fact(n=2)2 * 1 = 2call stackfact(n=1) → 1pop: fact(1) → 12 * 1 = 2

fact(1) is popped and returns 1. fact(2) resumes: 2 * 1 = 2.

main()fact(n=3)3 * 2 = 6call stackfact(n=2) → 2pop: fact(2) → 23 * 2 = 6

fact(2) is popped and returns 2. fact(3) resumes: 3 * 2 = 6.

main()call stackfact(n=3) → 6pop: fact(3) → 6println(6)

fact(3) = 6 is printed. Depth n → O(n) memory; recursion that goes too deep throws StackOverflowError.

int fact(int n) {
  if (n <= 1)
    return 1;
  return n * fact(n - 1);
}
// in main:
System.out.println(fact(3));

Applications · Step through the code

Reversing a String, Step by Step

str = "ABCD"

main calls reverse("ABCD").

str = "ABCD"emptystack

Create an empty stack of characters.

chars0A1B2C3Demptystack

Copy the string into a char array (we will overwrite it).

chars0A1B2C3D▲ cAstack

First loop: c = 'A' → push('A').

chars0A1B2C3D▲ cABstack

First loop: c = 'B' → push('B').

chars0A1B2C3D▲ cABCstack

First loop: c = 'C' → push('C').

chars0A1B2C3D▲ cABCDstack

First loop: c = 'D' → push('D'). Every character is on the stack, the last one on top.

chars0D1B2C3D▲ iABCDstack

Second loop, i = 0: pop() → 'D', chars[0] = 'D'. Items come out in reverse order.

chars0D1C2C3D▲ iABCstack

Second loop, i = 1: pop() → 'C', chars[1] = 'C'. Items come out in reverse order.

chars0D1C2B3D▲ iABstack

Second loop, i = 2: pop() → 'B', chars[2] = 'B'. Items come out in reverse order.

chars0D1C2B3A▲ iAstack

Second loop, i = 3: pop() → 'A', chars[3] = 'A'. Items come out in reverse order.

chars0D1C2B3Aemptystack

return "DCBA". Time and extra memory are O(n).

chars0D1C2B3Aemptystack

main prints the result: DCBA.

String reverse(String str) {
    Deque<Character> stack = new ArrayDeque<>();
    char[] chars = str.toCharArray();
    for (char c : chars)
        stack.push(c);
    for (int i = 0; i < str.length(); i++)
        chars[i] = stack.pop();
    return new String(chars);
}
// in main:
System.out.println(reverse("ABCD"));

Applications

Bracket Matching

Let the string S contain only the characters ( ) { } [ ]. S is balanced if and only if

  1. every opening bracket is closed by a bracket of the same type, and
  2. brackets are closed in the right order (the last one opened is the first one closed → LIFO!).
Algorithm: keep the unmatched opening brackets on a stack and scan S from left to right:
  • Opening bracket → push it.
  • Closing bracket → if the stack is empty, unbalanced; otherwise pop, and if the popped bracket is a different type, unbalanced.
  • At the end of S, an empty stack means balanced; anything left over is an unclosed bracket.
{[()]}balanced
([)]wrong order
(()unclosed
())extra closer

Applications

Bracket Matching: Java

boolean isBalanced(String s) {
  Deque<Character> stack = new ArrayDeque<>();
  for (char c : s.toCharArray()) {
    if (c == '(' || c == '[' || c == '{') {
      stack.push(c);
    } else {
      if (stack.isEmpty()) return false;
      char open = stack.pop();
      if (!matches(open, c)) return false;
    }
  }
  return stack.isEmpty();
}
Helper method
boolean matches(char open, char close) {
  return (open == '(' && close == ')')
      || (open == '[' && close == ']')
      || (open == '{' && close == '}');
}
Empty-stack checkWithout it, input such as "())" or "]" pops an empty stack → exception.
ComplexityO(n) time; in the worst case the stack needs O(n) memory.

Applications · Step through the code

Code Trace: isBalanced("{{[]}()}[]")

s0{1{2[3]4}5(6)7}8[9]emptystack

Create an empty stack to hold the unmatched opening brackets.

s0{1{2[3]4}5(6)7}8[9]▲ c{stack

'{' is an opening bracket → push. It waits for its closer.

s0{1{2[3]4}5(6)7}8[9]▲ c{{stack

'{' is an opening bracket → push. It waits for its closer.

s0{1{2[3]4}5(6)7}8[9]▲ c{{[stack

'[' is an opening bracket → push. It waits for its closer.

s0{1{2[3]4}5(6)7}8[9]▲ c{{[stack

']' is a closing bracket; the stack is not empty → pop the most recent opener, '['.

s0{1{2[3]4}5(6)7}8[9]▲ c{{stack

matches('[', ']') → true: same type, right order. Continue.

s0{1{2[3]4}5(6)7}8[9]▲ c{{stack

'}' is a closing bracket; the stack is not empty → pop the most recent opener, '{'.

s0{1{2[3]4}5(6)7}8[9]▲ c{stack

matches('{', '}') → true: same type, right order. Continue.

s0{1{2[3]4}5(6)7}8[9]▲ c{(stack

'(' is an opening bracket → push. It waits for its closer.

s0{1{2[3]4}5(6)7}8[9]▲ c{(stack

')' is a closing bracket; the stack is not empty → pop the most recent opener, '('.

s0{1{2[3]4}5(6)7}8[9]▲ c{stack

matches('(', ')') → true: same type, right order. Continue.

s0{1{2[3]4}5(6)7}8[9]▲ c{stack

'}' is a closing bracket; the stack is not empty → pop the most recent opener, '{'.

s0{1{2[3]4}5(6)7}8[9]▲ cemptystack

matches('{', '}') → true: same type, right order. Continue.

s0{1{2[3]4}5(6)7}8[9]▲ c[stack

'[' is an opening bracket → push. It waits for its closer.

s0{1{2[3]4}5(6)7}8[9]▲ c[stack

']' is a closing bracket; the stack is not empty → pop the most recent opener, '['.

s0{1{2[3]4}5(6)7}8[9]▲ cemptystack

matches('[', ']') → true: same type, right order. Continue.

s0{1{2[3]4}5(6)7}8[9]emptystack

The string is done and the stack is empty → return true: balanced. Each character was handled once → O(n).

boolean isBalanced(String s) {
  Deque<Character> stack = new ArrayDeque<>();
  for (char c : s.toCharArray()) {
    if (c == '(' || c == '[' || c == '{') {
      stack.push(c);
    } else {
      if (stack.isEmpty()) return false;
      char open = stack.pop();
      if (!matches(open, c)) return false;
    }
  }
  return stack.isEmpty();
}

Applications · Step through the code

Code Trace: isBalanced("([)]")

s0(1[2)3]emptystack

Create an empty stack to hold the unmatched opening brackets.

s0(1[2)3]▲ c(stack

'(' is an opening bracket → push. It waits for its closer.

s0(1[2)3]▲ c([stack

'[' is an opening bracket → push. It waits for its closer.

s0(1[2)3]▲ c([stack

')' is a closing bracket; the stack is not empty → pop the most recent opener, '['.

s0(1[2)3]▲ c([stackno match!

matches('[', ')') → false: ')' arrives, but the most recent opener is '['. return false. Likewise, (() fails because the stack is not empty at the end, and ()) because a closer meets an empty stack.

boolean isBalanced(String s) {
  Deque<Character> stack = new ArrayDeque<>();
  for (char c : s.toCharArray()) {
    if (c == '(' || c == '[' || c == '{') {
      stack.push(c);
    } else {
      if (stack.isEmpty()) return false;
      char open = stack.pop();
      if (!matches(open, c)) return false;
    }
  }
  return stack.isEmpty();
}

Applications

Next Greater Element

For each item in an array, find the first larger item to its right, or −1 if there is none.

arr473481
result7848−1−1
  • Brute force: scan to the right of every item → O(n²).
  • With a stack: scan the array right to left, keeping only “candidate” larger values → O(n).
  • From bottom to top, the stack stays in decreasing order (a monotonic stack).
int[] nextGreater(int[] arr) {
    int[] result = new int[arr.length];
    Deque<Integer> stack = new ArrayDeque<>();
    for (int i = arr.length-1; i >= 0; i--) {
        while (!stack.isEmpty() &&
               stack.peek() <= arr[i])
            stack.pop();
        result[i] = stack.isEmpty()
                 ? -1 : stack.peek();
        stack.push(arr[i]);
    }
    return result;
}

Applications · Step through the code

Code Trace: nextGreater({4, 7, 3, 4, 8, 1})

arrresult041723344851000000emptystack

Create result (all 0s) and an empty stack. The array is scanned right to left.

arrresult041723344851▲ i00000-1emptystack

i = 5 (arr[i] = 1): the stack is empty → −1.

arrresult041723344851▲ i00000-11stack

push(1): a new candidate for the items to the left. The stack stays decreasing from bottom to top.

arrresult041723344851▲ i00000-11stack

i = 4: the top, 1, is ≤ 8 → pop. 1 can never be the answer for anything left of 8 (the larger 8 is in the way).

arrresult041723344851▲ i0000-1-1emptystack

i = 4 (arr[i] = 8): the stack is empty → −1.

arrresult041723344851▲ i0000-1-18stack

push(8): a new candidate for the items to the left. The stack stays decreasing from bottom to top.

arrresult041723344851▲ i0008-1-18stack

i = 3 (arr[i] = 4): the top, 8, is > 4 → result[3] = 8.

arrresult041723344851▲ i0008-1-184stack

push(4): a new candidate for the items to the left. The stack stays decreasing from bottom to top.

arrresult041723344851▲ i0048-1-184stack

i = 2 (arr[i] = 3): the top, 4, is > 3 → result[2] = 4.

arrresult041723344851▲ i0048-1-1843stack

push(3): a new candidate for the items to the left. The stack stays decreasing from bottom to top.

arrresult041723344851▲ i0048-1-1843stack

i = 1: the top, 3, is ≤ 7 → pop. 3 can never be the answer for anything left of 7 (the larger 7 is in the way).

arrresult041723344851▲ i0048-1-184stack

i = 1: the top, 4, is ≤ 7 → pop. 4 can never be the answer for anything left of 7 (the larger 7 is in the way).

arrresult041723344851▲ i0848-1-18stack

i = 1 (arr[i] = 7): the top, 8, is > 7 → result[1] = 8.

arrresult041723344851▲ i0848-1-187stack

push(7): a new candidate for the items to the left. The stack stays decreasing from bottom to top.

arrresult041723344851▲ i7848-1-187stack

i = 0 (arr[i] = 4): the top, 7, is > 4 → result[0] = 7.

arrresult041723344851▲ i7848-1-1874stack

push(4): a new candidate for the items to the left. The stack stays decreasing from bottom to top.

arrresult0417233448517848-1-1874stack

return [7, 8, 4, 8, -1, -1]. Each item is pushed at most once and popped at most once → O(n).

int[] nextGreater(int[] arr) {
    int[] result = new int[arr.length];
    Deque<Integer> stack = new ArrayDeque<>();
    for (int i = arr.length-1; i >= 0; i--) {
        while (!stack.isEmpty() &&
               stack.peek() <= arr[i])
            stack.pop();
        result[i] = stack.isEmpty()
                 ? -1 : stack.peek();
        stack.push(arr[i]);
    }
    return result;
}

Applications · Step through the code

Code Trace: A Stack with O(1) getMin()

emptysemptyminpush(5)push(3)push(7)push(3)getMin()pop()getMin()pop()pop()getMin()top of min = current minimum

Two stacks: s holds every item, and min always has the current minimum on top. Goal: O(1) getMin().

5semptyminpush(5)push(3)push(7)push(3)getMin()pop()getMin()pop()pop()getMin()top of min = current minimum

push(5): s.push(5).

5s5minpush(5)push(3)push(7)push(3)getMin()pop()getMin()pop()pop()getMin()top of min = current minimum

min is empty → min.push(5): a new (or equal) minimum.

53s5minpush(5)push(3)push(7)push(3)getMin()pop()getMin()pop()pop()getMin()top of min = current minimum

push(3): s.push(3).

53s53minpush(5)push(3)push(7)push(3)getMin()pop()getMin()pop()pop()getMin()top of min = current minimum

3 <= 5 → true → min.push(3): a new (or equal) minimum.

537s53minpush(5)push(3)push(7)push(3)getMin()pop()getMin()pop()pop()getMin()top of min = current minimum

push(7): s.push(7).

537s53minpush(5)push(3)push(7)push(3)getMin()pop()getMin()pop()pop()getMin()top of min = current minimum

7 <= 3 → false: 7 is not a new minimum, so min does not change.

5373s53minpush(5)push(3)push(7)push(3)getMin()pop()getMin()pop()pop()getMin()top of min = current minimum

push(3): s.push(3).

5373s533minpush(5)push(3)push(7)push(3)getMin()pop()getMin()pop()pop()getMin()top of min = current minimum

3 <= 3 → true → min.push(3): a new (or equal) minimum.

5373s533minpush(5)push(3)push(7)push(3)getMin()→ 3pop()getMin()pop()pop()getMin()top of min = current minimum

getMin(): min.peek() = 3. No searching → O(1).

5373s533minpush(5)push(3)push(7)push(3)getMin()→ 3pop()→ 3getMin()pop()pop()getMin()top of min = current minimum

pop(): 3 comes off s. 3 == min.peek()? yes → pop min too; the new minimum is 3.

537s53minpush(5)push(3)push(7)push(3)getMin()→ 3pop()→ 3getMin()→ 3pop()pop()getMin()top of min = current minimum

getMin(): min.peek() = 3. No searching → O(1).

537s53minpush(5)push(3)push(7)push(3)getMin()→ 3pop()→ 3getMin()→ 3pop()→ 7pop()getMin()top of min = current minimum

pop(): 7 comes off s. 7 == min.peek()? no → min does not change.

53s53minpush(5)push(3)push(7)push(3)getMin()→ 3pop()→ 3getMin()→ 3pop()→ 7pop()→ 3getMin()top of min = current minimum

pop(): 3 comes off s. 3 == min.peek()? yes → pop min too; the new minimum is 5.

5s5minpush(5)push(3)push(7)push(3)getMin()→ 3pop()→ 3getMin()→ 3pop()→ 7pop()→ 3getMin()→ 5top of min = current minimum

getMin(): min.peek() = 5. No searching → O(1). Thanks to <=, the minimum stayed 3 after the second 3 was popped.

Deque<Integer> s = new ArrayDeque<>();
Deque<Integer> min = new ArrayDeque<>();
void push(int x) {
  s.push(x);
  if (min.isEmpty() || x <= min.peek())
    min.push(x);
}
int pop() {
  int x = s.pop();
  if (x == min.peek()) min.pop();
  return x;
}
int getMin() { return min.peek(); }

Towers of Hanoi

Towers of Hanoi: A Non-Recursive Solution with a Stack

Rules: each move takes the single top disk; a larger disk never goes on a smaller one.

// Task: move n disks from 'from' to 'to' using 'via'
record Task(int n, char from, char via, char to) {}

void hanoi(int n) {
  Deque<Task> stack = new ArrayDeque<>();
  stack.push(new Task(n, 'A', 'B', 'C'));
  while (!stack.isEmpty()) {
    Task t = stack.pop();
    if (t.n() == 1)
      System.out.println(t.from() + " → " + t.to());
    else {                         // reverse order
      stack.push(new Task(t.n()-1, t.via(), t.from(), t.to()));
      stack.push(new Task(1, t.from(), t.via(), t.to()));
      stack.push(new Task(t.n()-1, t.from(), t.to(), t.via()));
    }
  }
}
An explicit task stack takes the place of the call stack in the recursive solution. When n > 1, three subtasks are pushed in reverse order: 1. n−1 disks from source to auxiliary, 2. 1 disk from source to destination, 3. n−1 disks from auxiliary to destination, so that task 1 ends up on top.

Towers of Hanoi · Step through the code

Code Trace: hanoi(3) (with a stack)

(3,A,B,C)stack (n,from,via,to)A321BC

The initial task is (3, A, B, C): “move 3 disks from A to C, using B”.

(3,A,B,C)stack (n,from,via,to)A321BC

pop → t = (3,A,B,C): this is the task to do now.

(2,B,A,C)(1,A,B,C)(2,A,C,B)stack (n,from,via,to)A321BC

n = 3 > 1 → push three subtasks in reverse order; (2, A, C, B), which runs first, ends up on top (LIFO).

(2,B,A,C)(1,A,B,C)(2,A,C,B)stack (n,from,via,to)A321BC

pop → t = (2,A,C,B): this is the task to do now.

(2,B,A,C)(1,A,B,C)(1,C,A,B)(1,A,C,B)(1,A,B,C)stack (n,from,via,to)A321BC

n = 2 > 1 → push three subtasks in reverse order; (1, A, B, C), which runs first, ends up on top (LIFO).

(2,B,A,C)(1,A,B,C)(1,C,A,B)(1,A,C,B)(1,A,B,C)stack (n,from,via,to)A321BC

pop → t = (1,A,B,C): this is the task to do now.

(2,B,A,C)(1,A,B,C)(1,C,A,B)(1,A,C,B)stack (n,from,via,to)A32BC1

n = 1 → move directly: disk 1, A → C (move 1).

(2,B,A,C)(1,A,B,C)(1,C,A,B)(1,A,C,B)stack (n,from,via,to)A32BC1

pop → t = (1,A,C,B): this is the task to do now.

(2,B,A,C)(1,A,B,C)(1,C,A,B)stack (n,from,via,to)A3B2C1

n = 1 → move directly: disk 2, A → B (move 2).

(2,B,A,C)(1,A,B,C)(1,C,A,B)stack (n,from,via,to)A3B2C1

pop → t = (1,C,A,B): this is the task to do now.

(2,B,A,C)(1,A,B,C)stack (n,from,via,to)A3B21C

n = 1 → move directly: disk 1, C → B (move 3).

void hanoi(int n) {
  Deque<Task> stack = new ArrayDeque<>();
  stack.push(new Task(n, 'A', 'B', 'C'));
  while (!stack.isEmpty()) {
    Task t = stack.pop();
    if (t.n() == 1)
      System.out.println(t.from() + " → " + t.to());
    else {                         // reverse order
      stack.push(new Task(t.n()-1, t.via(), t.from(), t.to()));
      stack.push(new Task(1, t.from(), t.via(), t.to()));
      stack.push(new Task(t.n()-1, t.from(), t.to(), t.via()));
    }
  }
}

Towers of Hanoi · Step through the code

Code Trace: hanoi(3) (with a stack, continued)

(2,B,A,C)(1,A,B,C)stack (n,from,via,to)A3B21C

pop → t = (1,A,B,C): this is the task to do now.

(2,B,A,C)stack (n,from,via,to)AB21C3

n = 1 → move directly: disk 3, A → C (move 4).

(2,B,A,C)stack (n,from,via,to)AB21C3

pop → t = (2,B,A,C): this is the task to do now.

(1,A,B,C)(1,B,A,C)(1,B,C,A)stack (n,from,via,to)AB21C3

n = 2 > 1 → push three subtasks in reverse order; (1, B, C, A), which runs first, ends up on top (LIFO).

(1,A,B,C)(1,B,A,C)(1,B,C,A)stack (n,from,via,to)AB21C3

pop → t = (1,B,C,A): this is the task to do now.

(1,A,B,C)(1,B,A,C)stack (n,from,via,to)A1B2C3

n = 1 → move directly: disk 1, B → A (move 5).

(1,A,B,C)(1,B,A,C)stack (n,from,via,to)A1B2C3

pop → t = (1,B,A,C): this is the task to do now.

(1,A,B,C)stack (n,from,via,to)A1BC32

n = 1 → move directly: disk 2, B → C (move 6).

(1,A,B,C)stack (n,from,via,to)A1BC32

pop → t = (1,A,B,C): this is the task to do now.

emptystack (n,from,via,to)ABC321

n = 1 → move directly: disk 1, A → C (move 7).

emptystack (n,from,via,to)ABC321

The stack is empty → the loop ends. 7 = 2³ − 1 moves; every disk is on C.

void hanoi(int n) {
  Deque<Task> stack = new ArrayDeque<>();
  stack.push(new Task(n, 'A', 'B', 'C'));
  while (!stack.isEmpty()) {
    Task t = stack.pop();
    if (t.n() == 1)
      System.out.println(t.from() + " → " + t.to());
    else {                         // reverse order
      stack.push(new Task(t.n()-1, t.via(), t.from(), t.to()));
      stack.push(new Task(1, t.from(), t.via(), t.to()));
      stack.push(new Task(t.n()-1, t.from(), t.to(), t.via()));
    }
  }
}

Stock Span

The Stock Span Problem

  • An array holds daily closing prices.
  • The span of a day is the number of consecutive days, counting back from and including that day, whose price is less than or equal to that day's price.
  • In financial analysis it shows “for how many days” a price has not been beaten.
DayPriceSpanWhy?
11001first day
470260 ≤ 70, 80 > 70
675460, 70, 60 ≤ 75; 80 > 75
785675, 60, 70, 60, 80 ≤ 85
100d1180d2160d3170d4260d5175d6485d76

Prices [100, 80, 60, 70, 60, 75, 85] → spans [1, 1, 1, 2, 1, 4, 6]. The bold numbers under the bars are the spans.

Stock Span

Stock Span: An O(n) Solution with a Stack

int[] span(int[] prices) {
    int[] spans = new int[prices.length];
    Deque<Integer> stack = new ArrayDeque<>();
    stack.push(0); spans[0] = 1;
    for (int i = 1; i < prices.length; i++) {
        while (!stack.isEmpty() &&
            prices[i] >= prices[stack.peek()])
            stack.pop();
        spans[i] = stack.isEmpty() ? i + 1
                              : i - stack.peek();
        stack.push(i);
    }
    return spans;
}
What is on the stack?The indices of days not yet beaten (prices decrease from bottom to top).
SpanThe top is the nearest earlier day with a higher price: span = i − top, or i + 1 if the stack is empty.
ComplexityEach index is pushed once and popped at most once → O(n).

Stock Span · Step through the code

Code Trace: span({100, 80, 60, 70, 60, 75, 85})

100018016027036047558560 (100)stack

First day: push index 0; spans[0] = 1 (no earlier days).

1000180116027036047558560 (100)stack

i = 1 (price 80): the top, day 0 (100), is higher → span = 1 − 0 = 1.

1000180116027036047558560 (100)1 (80)stack

push(1): a possible boundary for later days.

10001801160217036047558560 (100)1 (80)stack

i = 2 (price 60): the top, day 1 (80), is higher → span = 2 − 1 = 1.

10001801160217036047558560 (100)1 (80)2 (60)stack

push(2): a possible boundary for later days.

10001801160217036047558560 (100)1 (80)2 (60)stack

i = 3: 70 ≥ prices[2] = 60 → pop. Day 2 can no longer be anyone's boundary.

100018011602170326047558560 (100)1 (80)stack

i = 3 (price 70): the top, day 1 (80), is higher → span = 3 − 1 = 2.

100018011602170326047558560 (100)1 (80)3 (70)stack

push(3): a possible boundary for later days.

1000180116021703260417558560 (100)1 (80)3 (70)stack

i = 4 (price 60): the top, day 3 (70), is higher → span = 4 − 3 = 1.

1000180116021703260417558560 (100)1 (80)3 (70)4 (60)stack

push(4): a possible boundary for later days.

1000180116021703260417558560 (100)1 (80)3 (70)4 (60)stack

i = 5: 75 ≥ prices[4] = 60 → pop. Day 4 can no longer be anyone's boundary.

1000180116021703260417558560 (100)1 (80)3 (70)stack

i = 5: 75 ≥ prices[3] = 70 → pop. Day 3 can no longer be anyone's boundary.

10001801160217032604175548560 (100)1 (80)stack

i = 5 (price 75): the top, day 1 (80), is higher → span = 5 − 1 = 4.

10001801160217032604175548560 (100)1 (80)5 (75)stack

push(5): a possible boundary for later days.

10001801160217032604175548560 (100)1 (80)5 (75)stack

i = 6: 85 ≥ prices[5] = 75 → pop. Day 5 can no longer be anyone's boundary.

10001801160217032604175548560 (100)1 (80)stack

i = 6: 85 ≥ prices[1] = 80 → pop. Day 1 can no longer be anyone's boundary.

100018011602170326041755485660 (100)stack

i = 6 (price 85): the top, day 0 (100), is higher → span = 6 − 0 = 6.

100018011602170326041755485660 (100)6 (85)stack

push(6): a possible boundary for later days.

100018011602170326041755485660 (100)6 (85)stack

return [1, 1, 1, 2, 1, 4, 6]. Each index is pushed once and popped at most once → O(n).

int[] span(int[] prices) {
    int[] spans = new int[prices.length];
    Deque<Integer> stack = new ArrayDeque<>();
    stack.push(0); spans[0] = 1;
    for (int i = 1; i < prices.length; i++) {
        while (!stack.isEmpty() &&
            prices[i] >= prices[stack.peek()])
            stack.pop();
        spans[i] = stack.isEmpty() ? i + 1
                              : i - stack.peek();
        stack.push(i);
    }
    return spans;
}

Expression Notation

Infix, Prefix, and Postfix Notation

NotationWhere the operator goesA * (B + C) / D
Infixbetween the operandsA * ( B + C ) / D
Prefix · Polish notationbefore the operands/ * A + B C D
Postfix · reverse Polish (RPN)after the operandsA B C + * D /
  • Prefix and postfix need no parentheses and no precedence rules: a stack evaluates the expression in a single left-to-right (or right-to-left) pass.
  • Compilers and interpreters usually translate expressions into postfix form (e.g., stack-based bytecode), and some calculators use RPN.
  • They also appear in symbolic algebra and in work with expression trees.
Careful: the prefix form * A / + B C D is wrong; it means A * ((B + C) / D). The correct prefix, which keeps the left-to-right order of operations, is / * A + B C D.

Expression Notation

Precedence, Associativity, and Converting by Hand

OperatorPrecedenceAssociativity
^ (power)3right to left
* /2left to right
+ −1left to right

Equal precedence groups left to right: A − B + C = (A − B) + C → postfix A B − C +.

By hand: full parenthesization
  1. Parenthesize every operation: ((A * (B + C)) / D)
  2. Postfix: move each operator to the right of its parentheses → A B C + * D /
  3. Prefix: move each operator to the left of its parentheses → / * A + B C D
The operands (A, B, C, D) appear in the same order in all three notations; only the operators move. That makes a quick check on any conversion.

Expression Notation

Infix → Postfix: The Algorithm

Read the input from left to right, holding operators and opening parentheses on a stack.

Token readAction
Operand (A, B, 5 …)Write it straight to the output.
(Push it.
)Pop to the output until you reach (; discard the (.
Operator opWhile the top is not ( and has precedence ≥ op, pop it to the output; then push op.
End of inputPop every remaining operator to the output.

The “≥” rule gives left-to-right associativity. For right-associative ^, pop only operators with precedence >. Time O(n).

Expression Notation

Infix → Postfix: Java

String infixToPostfix(String expr) {
  StringBuilder out = new StringBuilder();
  Deque<Character> stack = new ArrayDeque<>();
  for (char c : expr.toCharArray()) {
    if (Character.isLetterOrDigit(c)) out.append(c);
    else if (c == '(') stack.push(c);
    else if (c == ')') {
      while (stack.peek() != '(')
        out.append(stack.pop());
      stack.pop();                  // discard '('
    } else {                        // operator
      while (!stack.isEmpty() &&
          prec(stack.peek()) >= prec(c))
        out.append(stack.pop());
      stack.push(c);
    }
  }
  while (!stack.isEmpty()) out.append(stack.pop());
  return out.toString();
}
int prec(char op) {
  switch (op) {
    case '+': case '-': return 1;
    case '*': case '/': return 2;
    default: return 0;   // '('
  }
}
AssumptionsSingle-character operands, only + − * / (no ^), no spaces, and a valid expression.
Why ( → 0?An incoming operator never pops (; only ) discards it.

Expression Notation · Step through the code (loop only)

Code Trace: infixToPostfix("A*(B+C)/D")

exprA*(B+C)/Dout:emptystack

The input is read left to right. Operands go to the output; operators and '(' go on the stack.

exprA*(B+C)/D▲ cout:Aemptystack

A is an operand → straight to the output.

exprA*(B+C)/D▲ cout:A*stack

*: the stack is empty → push *.

exprA*(B+C)/D▲ cout:A*(stack

( → always pushed. It shields the operators inside until it is closed.

exprA*(B+C)/D▲ cout:AB*(stack

B is an operand → straight to the output.

exprA*(B+C)/D▲ cout:AB*(+stack

+: '(' is on top → push +.

exprA*(B+C)/D▲ cout:ABC*(+stack

C is an operand → straight to the output.

exprA*(B+C)/D▲ cout:ABC+*(+stack

): pop until '(' → + goes to the output.

exprA*(B+C)/D▲ cout:ABC+*(stack

'(' is popped and discarded; parentheses never reach the output.

exprA*(B+C)/D▲ cout:ABC+**stack

/: precedence of * on top (2) = precedence of / (2) → * goes to the output.

exprA*(B+C)/D▲ cout:ABC+*/stack

/: the stack is empty → push /.

exprA*(B+C)/D▲ cout:ABC+*D/stack

D is an operand → straight to the output.

exprA*(B+C)/Dout:ABC+*D//stack

End of input: the remaining operator / goes to the output. Result: A B C + * D /

for (char c : expr.toCharArray()) {
  if (Character.isLetterOrDigit(c)) out.append(c);
  else if (c == '(') stack.push(c);
  else if (c == ')') {
    while (stack.peek() != '(')
      out.append(stack.pop());
    stack.pop();                  // discard '('
  } else {                        // operator
    while (!stack.isEmpty() &&
        prec(stack.peek()) >= prec(c))
      out.append(stack.pop());
    stack.push(c);
  }
}
while (!stack.isEmpty()) out.append(stack.pop());

Expression Notation · Step through the code (loop only)

Code Trace: infixToPostfix("A+B*C-D/E")

exprA+B*C-D/Eout:emptystack

The input is read left to right. Operands go to the output; operators and '(' go on the stack.

exprA+B*C-D/E▲ cout:Aemptystack

A is an operand → straight to the output.

exprA+B*C-D/E▲ cout:A+stack

+: the stack is empty → push +.

exprA+B*C-D/E▲ cout:AB+stack

B is an operand → straight to the output.

exprA+B*C-D/E▲ cout:AB+*stack

*: precedence of + on top (1) < 2 → push *.

exprA+B*C-D/E▲ cout:ABC+*stack

C is an operand → straight to the output.

exprA+B*C-D/E▲ cout:ABC*+*stack

-: precedence of * on top (2) > precedence of - (1) → * goes to the output.

exprA+B*C-D/E▲ cout:ABC*++stack

-: precedence of + on top (1) = precedence of - (1) → + goes to the output.

exprA+B*C-D/E▲ cout:ABC*+-stack

-: the stack is empty → push -.

exprA+B*C-D/E▲ cout:ABC*+D-stack

D is an operand → straight to the output.

exprA+B*C-D/E▲ cout:ABC*+D-/stack

/: precedence of - on top (1) < 2 → push /.

exprA+B*C-D/E▲ cout:ABC*+DE-/stack

E is an operand → straight to the output.

exprA+B*C-D/Eout:ABC*+DE/-/stack

End of input: the remaining operator / goes to the output.

exprA+B*C-D/Eout:ABC*+DE/--stack

End of input: the remaining operator - goes to the output. Result: A B C * + D E / − = (A + B·C) − D/E

for (char c : expr.toCharArray()) {
  if (Character.isLetterOrDigit(c)) out.append(c);
  else if (c == '(') stack.push(c);
  else if (c == ')') {
    while (stack.peek() != '(')
      out.append(stack.pop());
    stack.pop();                  // discard '('
  } else {                        // operator
    while (!stack.isEmpty() &&
        prec(stack.peek()) >= prec(c))
      out.append(stack.pop());
    stack.push(c);
  }
}
while (!stack.isEmpty()) out.append(stack.pop());

Expression Notation

Evaluating a Postfix Expression

  • Read left to right.
  • Number → push it.
  • Operator → pop twice, the right operand first and then the left; push the result.
  • At the end, the one item left on the stack is the answer. No parentheses or precedence → O(n).
int apply(int left, char op, int right) {
  switch (op) {
    case '+': return left + right;
    case '-': return left - right;
    case '*': return left * right;
    default:  return left / right;
  }
}
int evaluatePostfix(String expr) {
  Deque<Integer> stack = new ArrayDeque<>();
  for (char c : expr.toCharArray()) {
    if (Character.isDigit(c)) {
      stack.push(c - '0');
    } else {
      int right = stack.pop();
      int left = stack.pop();
      stack.push(apply(left, c, right));
    }
  }
  return stack.pop();
}

Assumes single-digit numbers, no spaces, and a valid expression.

Expression Notation · Step through the code

Code Trace: evaluatePostfix("412+*3/")

expr412+*3/emptystack

With A=4, B=1, C=2, D=3, A B C + * D / → "412+*3/". Create an empty operand stack.

expr412+*3/▲ c4stack

4 is a digit → push c - '0' = 4.

expr412+*3/▲ c41stack

1 is a digit → push c - '0' = 1.

expr412+*3/▲ c412stack

2 is a digit → push c - '0' = 2.

expr412+*3/▲ c2right1left4stack

+ is an operator: pop right = 2 first, then left = 1 (order matters for − and /).

expr412+*3/▲ c1 + 2 = 343stack

apply(1, '+', 2) = 3 → push the result back.

expr412+*3/▲ c3right4leftemptystack

* is an operator: pop right = 3 first, then left = 4 (order matters for − and /).

expr412+*3/▲ c4 * 3 = 1212stack

apply(4, '*', 3) = 12 → push the result back.

expr412+*3/▲ c123stack

3 is a digit → push c - '0' = 3.

expr412+*3/▲ c3right12leftemptystack

/ is an operator: pop right = 3 first, then left = 12 (order matters for − and /).

expr412+*3/▲ c12 / 3 = 44stack

apply(12, '/', 3) = 4 → push the result back.

expr412+*3/result = 4emptystack

The expression is done; the single value on the stack is the answer: return 4. No parentheses or precedence needed → O(n).

int evaluatePostfix(String expr) {
  Deque<Integer> stack = new ArrayDeque<>();
  for (char c : expr.toCharArray()) {
    if (Character.isDigit(c)) {
      stack.push(c - '0');
    } else {
      int right = stack.pop();
      int left = stack.pop();
      stack.push(apply(left, c, right));
    }
  }
  return stack.pop();
}

Expression Notation

Infix → Prefix Conversion

  1. Reverse the infix expression.
  2. Swap the parentheses: ( ↔ ).
  3. Run the postfix algorithm, but don't pop on equal precedence; pop only operators with precedence >.
  4. Reverse the output.
StepA * (B + C) / D
1–2. Reverse, swap parenthesesD / ( C + B ) * A
3. Postfix (no pop on equal)D C B + A * /
4. Reverse → prefix/ * A + B C D
In step 3, when * arrives, / of equal precedence is on top and is not popped. After the final reversal this preserves left associativity: first A * (B + C), then / D.
ExampleA + B * C − D / E → − + A * B C / D E
Prefix evaluationLike postfix, but read the expression right to left; the first operand popped is the left one.

Expression Notation · Step through the code

Code Trace: infixToPrefix("A*(B+C)/D")

exprA*(B+C)/D

main calls infixToPrefix("A*(B+C)/D").

exprA*(B+C)/Dt = rev(expr)D/)C+B(*A

1. The string is reversed. The parentheses flip too: ')' is now on the left, '(' on the right.

exprA*(B+C)/Dt = rev(expr)D/)C+B(*At = swapParens(t)D/(C+B)*A

2. Swap ( ↔ ); now it is a valid infix expression: D / (C + B) * A.

exprA*(B+C)/Dt = rev(expr)D/)C+B(*At = swapParens(t)D/(C+B)*Ap (no pop on equal)DCB+A*/

3. The postfix algorithm, but without popping on equal precedence → p = D C B + A * /.

exprA*(B+C)/Dt = rev(expr)D/)C+B(*At = swapParens(t)D/(C+B)*Ap (no pop on equal)DCB+A*/rev(p) → prefix/*A+BCD

4. Reverse p → returns / * A + B C D.

exprA*(B+C)/Dt = rev(expr)D/)C+B(*At = swapParens(t)D/(C+B)*Ap (no pop on equal)DCB+A*/rev(p) → prefix/*A+BCD

main prints the result. Check: the operands are still in the order A, B, C, D.

String rev(String s) {
  return new StringBuilder(s).reverse().toString();
}
String infixToPrefix(String expr) {
  String t = rev(expr);            // 1. reverse
  t = swapParens(t);               // 2. ( ↔ )
  String p = postfixForPrefix(t);  // 3. no pop on equal
  return rev(p);                   // 4. reverse
}
// in main:
System.out.println(infixToPrefix("A*(B+C)/D"));

Expression Notation

Step 2: swapParens

Reversing also flips the direction of the parentheses. This method writes ) for every ( and ( for every ), leaving all other characters unchanged.

String swapParens(String s) {
  StringBuilder sb = new StringBuilder();
  for (char c : s.toCharArray()) {
    if (c == '(') sb.append(')');
    else if (c == ')') sb.append('(');
    else sb.append(c);
  }
  return sb.toString();
}
t (reversed)D/)C+B(*AswapParens(t)D/(C+B)*A

One pass over the string: O(n) time, O(n) extra memory.

Expression Notation · Step through the code (loop only)

Code Trace: postfixForPrefix("D/(C+B)*A")

t (reversed, parentheses swapped)D/(C+B)*Aout:emptystack

The input is read left to right. Operands go to the output; operators and '(' go on the stack.

t (reversed, parentheses swapped)D/(C+B)*A▲ cout:Demptystack

D is an operand → straight to the output.

t (reversed, parentheses swapped)D/(C+B)*A▲ cout:D/stack

/: the stack is empty → push /.

t (reversed, parentheses swapped)D/(C+B)*A▲ cout:D/(stack

( → always pushed. It shields the operators inside until it is closed.

t (reversed, parentheses swapped)D/(C+B)*A▲ cout:DC/(stack

C is an operand → straight to the output.

t (reversed, parentheses swapped)D/(C+B)*A▲ cout:DC/(+stack

+: '(' is on top → push +.

t (reversed, parentheses swapped)D/(C+B)*A▲ cout:DCB/(+stack

B is an operand → straight to the output.

t (reversed, parentheses swapped)D/(C+B)*A▲ cout:DCB+/(+stack

): pop until '(' → + goes to the output.

t (reversed, parentheses swapped)D/(C+B)*A▲ cout:DCB+/(stack

'(' is popped and discarded; parentheses never reach the output.

t (reversed, parentheses swapped)D/(C+B)*A▲ cout:DCB+/*stack

*: precedence of / on top (2) = 2, and we don't pop on equal → push *.

t (reversed, parentheses swapped)D/(C+B)*A▲ cout:DCB+A/*stack

A is an operand → straight to the output.

t (reversed, parentheses swapped)D/(C+B)*Aout:DCB+A*/*stack

End of input: the remaining operator * goes to the output.

t (reversed, parentheses swapped)D/(C+B)*Aout:DCB+A*//stack

End of input: the remaining operator / goes to the output. p = D C B + A * / → reversed: / * A + B C D

for (char c : expr.toCharArray()) {
  if (Character.isLetterOrDigit(c)) out.append(c);
  else if (c == '(') stack.push(c);
  else if (c == ')') {
    while (stack.peek() != '(')
      out.append(stack.pop());
    stack.pop();                  // discard '('
  } else {                        // operator
    while (!stack.isEmpty() &&
        prec(stack.peek()) > prec(c))
      out.append(stack.pop());
    stack.push(c);
  }
}
while (!stack.isEmpty()) out.append(stack.pop());

Summary

Common Mistakes

pop / peek on an empty stackCall isEmpty() first; otherwise you get an exception or null, depending on the library. In bracket matching, "())" triggers exactly this bug.
Mixing up top and sizeIn an array stack, top is the index of the last item, so size = top + 1. The stack is full when top == capacity − 1.
Not clearing a popped cellIn an array of objects, skipping data[top--] = null keeps the object away from the garbage collector (a memory leak called loitering).
Operand order in postfixThe first pop is the right operand and the second the left: 5 2 − = 5 − 2 = 3, not 2 − 5.
Iteration orderA for-each over ArrayDeque goes top to bottom, but the legacy Stack goes bottom to top, so their output looks reversed.
Infinite recursionWithout a base case, frames pile up until a StackOverflowError occurs (the call stack overflows).

Lab · Java code, step by step

Lab: Bracket Matching and Postfix

Summary

Summary: Problems Solved with a Stack

ProblemWhat the stack holdsTime
push / pop / top / isEmpty / size—O(1)
Reversing a stringcharactersO(n)
Bracket matchingunmatched opening bracketsO(n)
Next greater elementcandidate values to the right (decreasing)O(n)
Stock spanindices of days not yet beatenO(n)
Towers of Hanoisubtasks (n, source, aux., destination)O(2n)
Infix → postfix / prefixoperators and (O(n)
Postfix evaluationintermediate resultsO(n)
Min stack (getMin)the current minimumO(1)
Back / forward navigationvisited pages (two stacks)O(1)

The common pattern: when we care about “the most recent item that is still unresolved,” we reach for a stack (LIFO).

Summary

Interactive Simulators

Stack Simulatorpush, pop, and peek, shown on both an array and a linked list.
Stock Span SimulatorHow the stack changes as the price array is processed.
Infix → Postfix ConverterThe operator stack and the output, token by token.
Infix → Prefix ConverterThe reversal method, step by step.

Summary

Sample Problems

  1. Starting from an empty stack, we run push(5), push(3), pop(), push(8), push(1), pop(), pop(), push(4). What do the pop() calls return, and what is left on the stack?
  2. Which of {[()()]} and {[(])} is balanced? In the other one, at which character is the error detected?
  3. Convert (A + B) * (C − D) / E to postfix and to prefix.
  4. What is the value of the postfix expression 5 1 2 + 4 * + 3 −?
  5. Find the spans for the prices [10, 4, 5, 90, 120, 80].
  6. Find the next greater element of each item in {2, 5, 3, 1, 4}.
Show answers
  1. The pops return 3, 1, 8. The stack ends as (bottom to top) 5, 4.
  2. {[()()]} is balanced. {[(])}: when ] arrives, ( is on top → unbalanced.
  3. Postfix: A B + C D − * E / · Prefix: / * + A B − C D E
  4. 1+2 = 3, 3·4 = 12, 5+12 = 17, 17−3 = 14.
  5. [1, 1, 2, 4, 5, 1]
  6. {5, −1, 4, 4, −1}.

Summary

Exercises

  1. We push 1, 2, 3, 4 in that order, with pops mixed in anywhere. Are the output orders 2 4 3 1 and 4 1 2 3 possible?
  2. Two stacks share one array (one grows from the front, the other from the back). When do they overflow?
  3. Sort a stack using a single auxiliary stack. What is the complexity?
  4. Use a stack to check whether a word is a palindrome.
  5. How would you check that HTML tags such as <b><i>…</i></b> are properly nested?
  6. If a full array grew by +10 instead of doubling, what would n pushes cost in total?
Hints / answers
  1. 2 4 3 1 yes; 4 1 2 3 no (once 4 is out, 3 is on top).
  2. top1 starts at −1 and top2 at N; full when top1 + 1 == top2.
  3. Pop x; move the auxiliary stack's items smaller than x back, then push x → O(n²).
  4. Push the letters, pop them to build the reversed string, and compare → O(n).
  5. The bracket algorithm: push each opening tag; on a closing tag, pop and compare names.
  6. The copies total 10 + 20 + 30 + … ≈ n²/20 → O(n²); O(n) per push.

Summary

Further Reading

  • Goodrich, Tamassia, and Goldwasser, Data Structures and Algorithms in Java, 6th ed., 2014.
  • Lafore, Data Structures and Algorithms in Java, 2nd ed., 2002.
  • Oracle, Java SE 17 API documentation for Deque and ArrayDeque.

The End

Chapter 7: Stacks
Next deck: Chapter 8: Queues and Deques · Study questions · Simulators: Stack, Stock Span, Infix → Prefix Converter, Infix → Postfix Converter · Glossary
1 / 1 Sercan KÜLCÜ. All rights reserved.