Data Structures · Lecture Notes
Chapter 7: Stacks
The stack is among the simplest data structures and among the most useful. Items enter and leave at one end only, so the last one in is the first one out. What looks like a restriction is what lets every basic operation run in constant time, and it suits the many problems of the form “close the most recently opened thing first.” We start from the abstract definition, implement the stack with an array and with a linked list, and see why a dynamic array grows by doubling. Then come applications: browser history, the call stack, balanced parentheses, next greater element, Towers of Hanoi, and stock span. The chapter ends by converting arithmetic expressions between infix, prefix, and postfix notation and evaluating postfix expressions.
Learning outcomes
- The LIFO model
- Explain last-in, first-out behavior and give a stack’s state and return values after a sequence of operations.
- Implementation
- Implement a stack with an array and a linked list, handling overflow, underflow, and dynamic growth correctly.
- Applications
- Use a stack for balanced parentheses, expression conversion and evaluation, call management, back/forward navigation, next greater element, and stock span.
- Cost analysis
- Give and justify the time and space cost of the basic operations; compute the amortized cost of doubling.
7.1The stack ADT
A data structure can be described at two levels. The first says what it does: which operations exist, what each changes and returns. The second says how: how the data is laid out in memory and what steps each operation takes. A first-level description is an abstract data type (ADT). This section treats the stack as an ADT; implementation comes next.
7.1.1What is a stack?
Picture a pile of clean plates. A washed plate always goes on top, and you take plates from the top. To reach the bottom one you must lift off every plate above it. Nobody pulls a plate from the middle; it is awkward, and the pile falls over.
A stack imitates this arrangement. Items are kept in sequence, but only one end, the top, is accessible, and both insertion and removal happen there. The item that comes off is therefore always the most recently added one still on the stack.
A stack is an abstract data type that keeps items in sequence and allows insertion and removal only at one end, the top. This discipline is called LIFO (last-in, first-out), or equivalently FILO (first-in, last-out).
Push 1, 2, 3 and then pop three times, and you get 3, 2, 1: the last in came out first (LIFO), the first in came out last (FILO).
The discipline turns up all over software:
- Undo: A text editor pushes every change onto a stack. “Undo” reverses the latest change, then the one before, so changes are undone in reverse order.
- The browser’s “back” button: Visited pages sit on a stack, and “back” returns to the page you left last. Section 7.3.2 develops this with two stacks.
- Function calls: If
maincallsfandfcallsg, thengfinishes first, thenf, thenmain. Languages therefore track calls on a stack (7.3.3).
Each case is about the most recently started job that has not finished. When it ends, we return to the job started just before it, and a stack keeps this list of waiting jobs as cheaply as possible.
Thinking of a stack as a list you can read in any order. Only the top item is visible; reaching a middle item means popping everything above it. The stack ADT has no “read the third item” operation.
7.1.2Basic operations
Five operations define the stack ADT. Their names are standard and match the Java library: push adds an item on top, pop removes the top item.
| Operation | Description |
|---|---|
push(x) | Adds a new item x on top. |
pop() | Removes and returns the top item. |
top() | Returns the top item without removing it (peek() in the Java library). |
isEmpty() | Returns true if the stack is empty, else false. |
size() | Returns the number of items. |
Only push and pop change the stack, adding or removing one item; the other three only report. top() and pop() both return the top item, but top() leaves it in place. When you must look at an item before deciding whether to remove it (as in the next greater element problem, 7.3.9), call top() and then pop().
On an empty stack there is no top item, so pop() and top() have nothing to return. Popping an empty stack is an error called underflow. Pushing onto a full stack of limited capacity is called overflow, which can only happen to bounded stacks (7.2.1).
7.1.3push and pop, step by step
Starting from an empty stack, we apply push(10), push(15), push(20), top(), and four pop() calls.
- Start. The stack is empty:
size = 0. push(10): As the only item, 10 is both bottom and top.size = 1.push(15): 15 goes on 10 and becomes the top.size = 2.push(20): Bottom to top, the stack is 10, 15, 20.size = 3.top(): 20 is returned but not removed.sizestays 3.pop(): 20, last in, comes out first. The top is 15;size = 2.pop(): 15 comes out. The top is 10;size = 1.pop(): 10 comes out; the stack is empty,size = 0.pop(): Nothing to remove: underflow, reported as an error.
The returned values are 20 (top), 20, 15, 10, and an error. The items left in the order 20, 15, 10, the reverse of their arrival.
push calls the stack holds 10, 15, 20 bottom to top, and top points to 20. Bottom: three pop calls have emptied the stack, and the fourth underflows. The tables give each operation’s return value and the size after it.The table in Figure 7.1 is a handy layout for stack exercises: one row per operation, with its return value and the size afterward. Writing down the stack itself, bottom to top, at each step keeps you from popping the wrong item.
Believing that top() changes the stack. It only reads: above, the first pop() after top() still returns 20. Had top() removed the item, that pop() would return 15.
7.1.4A Java interface: separating the ADT from its implementation
In Java an ADT is naturally expressed as an interface, which lists the methods a class must provide without giving their bodies. For the stack:
public interface Stack<E> {
int size();
boolean isEmpty();
void push(E e);
E top(); // null if empty
E pop(); // exception if empty
}The <E> makes the stack generic. E is a type parameter: write Stack<Integer> for integers or Stack<Character> for characters, and E stands for that type throughout, so one piece of code serves every item type. The methods are those of Table 7.1, and the comments make empty-stack behavior part of the contract: top() returns null, while pop() throws an exception.
The interface separates “what” from “how”:
- The ADT answers “what?” It names the operations and their meaning and says nothing about arrays or linked lists.
- The implementation answers “how?” A class such as
ArrayStack<E> implements Stack<E>writes each method for one storage scheme; a linked-list class can implement the same interface. - Code written against the interface can swap implementations. A method taking a
Stack<Integer>accepts an array-based or a linked stack without a line of the caller changing.
This matters in practice: if measurements call for a faster implementation, the interface keeps the change local.
Empty-stack behavior, however, differs between libraries:
| On an empty stack | pop() | top() / peek() |
|---|---|---|
Our own Stack<E> | throws an exception | returns null |
java.util.Stack | EmptyStackException | EmptyStackException |
java.util.ArrayDeque | NoSuchElementException | returns null |
Because of the differences in Table 7.2, always check isEmpty() before calling pop() or peek(). Otherwise the same code throws with one library and silently gets null with another, and the bug surfaces much later, where it is hard to trace.
Using our Stack interface and java.util.Stack in the same file. With import java.util.*; or import java.util.Stack;, the compiler may find the name ambiguous or pick the class you did not mean. If you use our interface, do not import java.util.Stack.
7.1.5Algorithms for the operations
Before implementing, we write the operations in language-neutral pseudocode. Assume the stack lives in an array, along with an integer top holding the index of the top item. Indices start at 0, so the first item goes into cell 0, and an empty stack is represented by top = −1.
push(x):
begin
if the stack is full // top = cap−1
error: overflow
else
increment top by 1
stack[top] ← x
endWith capacity cap, the last valid index is cap − 1; once top reaches it, the array is full and a push overflows. Otherwise top is incremented and x is written into the new top cell. The order matters: done the other way round, x would overwrite the current top item.
pop():
begin
if the stack is empty // top = −1
error: underflow
else
x ← stack[top]; decrement top by 1
return x
endIf top = −1, we have underflow. Otherwise the top item is saved, top is decremented, and the saved item is returned; again, read first, then decrement.
top():
begin
if the stack is empty return null
else return stack[top]
endisEmpty() and size():
isEmpty: true if top = −1, else false
size: return top + 1Why top + 1? The items occupy indices 0 through top, which is top + 1 indices. A three-item stack uses cells 0, 1, 2, so top = 2 and size = 3; an empty stack has top = −1 and size = 0.
Confusing top with size. top is the index of the last item, not the item count. The mix-up causes two typical bugs: testing fullness with top == cap instead of top == cap − 1, and having size() return top instead of top + 1. With the first, one more push is attempted on a full array, and stack[cap] falls outside it.
7.1.6Time and space cost
The stack’s great strength is that all its basic operations take constant time. An operation whose running time does not depend on the number of items takes O(1) time. Table 7.3 gives the costs and their reasons.
| Operation | Time | Why? |
|---|---|---|
push | O(1) | only the top changes |
pop | O(1) | only the top changes |
top | O(1) | the top is known directly |
isEmpty, size | O(1) | a counter or index is kept |
| searching for an item | O(n) | items must be popped one by one |
The pseudocode of 7.1.5 shows why. Each operation does at most one comparison, one index update, and one array access, and accessing a known index takes the same time whatever the array’s size. There are no loops; nothing is shifted or scanned. A push onto a million-item stack takes as many steps as onto a three-item one.
Searching is different. The ADT’s only way to find a value is to pop items from the top and look at each. In the worst case (the value is at the bottom or absent) all n items come off: O(n). If you search often, a stack is the wrong structure.
A stack of n items must store them all, so it uses O(n) memory, while each operation needs only a few local variables: O(1) extra space.
There is one exception. In a stack built on a dynamic array, which grows when full, the push that triggers growth copies every item into a new array and takes O(n). Growth is rare, so the average cost per push over a long sequence is still constant, or amortized O(1); Section 7.2.6 works it out.
The speed comes from the restriction itself: with access limited to the top, no operation ever shifts or searches. A list that allows insertion in the middle or reading any index is more flexible, but pays with operations that take O(n).
7.2Implementation
To implement an ADT is to write it as a class built on a particular storage scheme. For a stack there are two basic options, an array or a list of linked nodes. This section codes both and compares them.
7.2.1Fixed-size and dynamic-size stacks
First decide how many items the stack can hold; the two answers give two kinds of stack.
A fixed-size stack sets its capacity at creation and never grows or shrinks; underneath is a fixed-length array. Pushing onto a full stack is an overflow error, popping an empty one an underflow error.
A dynamic-size stack grows and shrinks at run time. When full it raises its own capacity, say by doubling, and when the item count falls far below capacity it may shrink to return memory. It is built on a dynamic array or a linked list.
push(50) overflows. Right: a dynamic stack in the same situation allocates an array of twice the capacity (8 cells), copies the old items, and writes 50 at index 4 of the new array.The two differ only when full. A dynamic stack can grow, but popping an empty one is still meaningless, so overflow occurs only in a fixed-size stack while underflow can occur in any.
7.2.2Two ways to implement a stack
The stack ADT can be implemented with an array or a linked list. Figure 7.3 shows the same stack (10, 15, 20 bottom to top) both ways.
top = 2; push increments top, pop decrements it. Right, linked: top points to node 20, the chain is 20 → 15 → 10 → null, and push and pop work at the head of the list.In the array-based stack, push increments top and writes the item into arr[top]; pop takes arr[top] and decrements top. The bottom is always cell 0, and the stack grows toward the end of the array.
In the linked stack, the top is the list’s head: push inserts a node there, and pop removes it and returns its value. The pictures differ, yet as ADTs the stacks are identical: both have 20 on top, and a pop returns 20 from either.
7.2.3The array-based stack
An array-based stack whose capacity is fixed at creation is also called a bounded stack. Figure 7.4 shows three items (5, 8, 3) in an array of capacity 8.
data, of capacity 8, holds 5, 8, 3. top = 2 marks the last item, so size = top + 1 = 3. Cells 3–7 are unused capacity and contain null.Its advantages:
- Simplicity. An array and an integer suffice; the code is a few lines.
- Memory economy. Only the items are stored, with no per-node reference as in a linked list.
- Strict memory control. The stack can never take more than its capacity, a safeguard against malicious input that tries to grow a stack until memory runs out.
- Cache friendliness. The items occupy consecutive cells, and since the processor reads memory in blocks, consecutive data is faster to reach than scattered nodes. The same O(1) operations thus usually run faster in practice.
Points to watch:
- Check for fullness before pushing; otherwise the write falls outside the array and Java throws
ArrayIndexOutOfBoundsException. - Capacity is a compromise: too small and the stack overflows, too large and idle cells waste memory.
- In an array of objects, set each vacated cell to
null, or the array keeps the removed object alive in memory for nothing.
7.2.4Code: ArrayStack
This class implements the Stack<E> interface of 7.1.4 with a fixed-capacity array.
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;
}
}How it works:
dataholds the items;top, the index of the top item, starts at-1because a new stack is empty.- The constructor allocates the array. Java forbids
new E[capacity], because the type parameterEis unknown at run time, so we create anObjectarray and cast it with(E[]). The compiler warns that the cast is “unchecked”; it is safe as long as the array never leaves the class. isEmpty()teststop == -1;size()returnstop + 1(7.1.5).top()returnsnullfor an empty stack, elsedata[top]. The conditional operator? :yields its first value if the condition holds, its second otherwise. The class has both a fieldtopand a methodtop(); Java tells them apart by the parentheses.push(x)throwsIllegalStateException("Overflow")iftop == data.length - 1, meaning the last cell is taken. Otherwisedata[++top] = x;runs.++topis a pre-increment:toprises first, and the new value serves as the index, fitting “increment, then write” (7.1.5) on one line.pop()throws an “Underflow” exception on an empty stack. Otherwise it saves the top item inx. Indata[top--] = null;,top--is a post-decrement: the oldtopis used as the index and that cell set tonull, and thentopdrops by one. Finallyxis returned.
++top versus top++ is decisive: data[top++] = x would overwrite the current top item, and data[--top] = null would clear the cell below the top.
No method loops, so each runs in O(1) time. The array has one cell per unit of capacity: O(capacity) space.
Not clearing the vacated cell. If pop only does top--, it returns the right value but the array still refers to the removed object. Java’s garbage collector frees only objects nothing refers to, so an object the stack has finished with but the array still references cannot be reclaimed. This kind of memory leak is called loitering. Arrays of primitives such as int[] hold no references and are immune.
7.2.5Code trace: ArrayStack
We trace a stack of capacity 4 as main calls s.push(10), s.push(15), s.push(20), s.top(), s.pop(), s.push(25), s.push(30), s.push(40). The method bodies involved:
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]; }| Call | Full/empty check | top | data | Returns |
|---|---|---|---|---|
| (start) | — | −1 | [null, null, null, null] | — |
push(10) | −1 == 3? no | 0 | [10, null, null, null] | — |
push(15) | 0 == 3? no | 1 | [10, 15, null, null] | — |
push(20) | 1 == 3? no | 2 | [10, 15, 20, null] | — |
top() | empty? no | 2 | [10, 15, 20, null] | 20 |
pop() | 2 == −1? no | 1 | [10, 15, null, null] | 20 |
push(25) | 1 == 3? no | 2 | [10, 15, 25, null] | — |
push(30) | 2 == 3? no | 3 | [10, 15, 25, 30] | — |
push(40) | 3 == 3? yes | 3 | [10, 15, 25, 30] | exception: “Overflow” |
On the first push(10), top = −1 differs from data.length − 1 = 3, so data[++top] = x raises top to 0 and writes data[0] = 10, one assignment regardless of array size.
top() returns data[2] = 20, changing nothing. The following pop() finds top = 2 ≠ −1, saves x = data[2] = 20, clears cell 2 with data[top--] = null, and lowers top to 1. It returns 20: last in, first out.
push(25) reuses cell 2, and push(30) fills the last cell, so top = 3. At push(40) the check yields 3 == 3: the array is full, IllegalStateException("Overflow") is thrown, and the array and top stay as they were. That is overflow in a fixed-capacity stack; a dynamic array would grow here instead.
null cells, top = −1. (b) After three push calls, top = 2. (c) pop() has returned 20; cell 2 is null, top = 1. (d) After push(25) and push(30) the array is full, top = 3; push(40) overflows.In frames (c) and (d) of Figure 7.5, cell 2 holds 20, becomes null after the pop, and is refilled with 25. An array-based stack reuses its cells; removing an item never shifts the rest.
7.2.6Dynamic arrays: doubling when full
A fixed capacity fails when the number of items is unknown in advance. The remedy is to move the items to a bigger array when the current one fills; since a Java array cannot change length, “growing” means creating a new array and copying.
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;
}Instead of throwing, the new push calls resize(2 * data.length), which allocates the larger array, copies the items at indices 0 to top to the same indices, and points data at the new array. The old array is now unreferenced, and the garbage collector reclaims it. Then data[++top] = x runs as usual.
A resizing push copies n items and so takes O(n). But resizes are rare: since the capacity doubles each time, a resize is followed by as many cheap pushes as there were items before it. The cost of the expensive operations is spread over the cheap ones in between.
The amortized cost of an operation is the total cost of a long sequence of operations divided by their number. Unlike the worst-case cost of a single operation, it averages occasional expensive operations with the cheap ones. No probability is involved: it is an upper bound valid for every sequence. Finding such a bound is called amortized analysis.
We push 17 times onto an empty dynamic-array stack of capacity 4. Each push does one write, and a resizing push also copies all current items. How many copies are made, and what is the total cost?
- Pushes 1–4: Writes only. After the 4th the array is full.
- Push 5:
top == 3 == data.length − 1; capacity goes from 4 to 8 and 4 items are copied. Then the 5th item is written. - Pushes 6–8: Writes only.
- Push 9: The array is full with 8 items; capacity becomes 16, and 8 items are copied.
- Pushes 10–16: Writes only.
- Push 17: 16 items are copied; capacity becomes 32.
- Total. Copies: 4 + 8 + 16 = 28. Writes: 17. Total 45 basic steps, below the bound 3n = 51: on average 45 / 17 ≈ 2.65 steps per
push.
This generalizes. The copy counts 4, 8, 16 each double the previous one, and such a sum is less than twice its last term. The last resize copies fewer than n items, so all copies together stay below 2n.
In a dynamic-array stack whose capacity doubles when full, n pushes starting from an empty stack cost less than 3n in total. Hence push has amortized cost O(1).
Let the initial capacity be 1 (a larger one only reduces copying). Resizes happen when the array fills with 1, 2, 4, …, 2k items, where 2k is the largest power of two below n. The copies number 1 + 2 + 4 + … + 2k = 2k+1 − 1, and since 2k < n, this is less than 2n − 1, hence less than 2n. Each push also does one write, n writes in all. The total is < 2n + n = 3n, an average of less than 3 per push, a constant. □
The theorem does not make every push fast: the 17th push of Example 7.2 made 16 copies. In a game or real-time system where every operation must be quick, only the worst case counts.
Growing by a fixed amount instead of a factor is far worse. Let the capacity start at 10 and grow by 10 each time it fills (10 → 20 → 30 → …). Resizes happen at 10, 20, 30, … items, and the copies total 10 + 20 + 30 + … . For n = 100 that is 10 + 20 + … + 90 = 450, and in general about n2/20. The total cost is O(n2), or O(n) per push on average. For 1000 pushes, doubling makes 1023 copies; growing by 10 makes 49,500.
Equating “amortized O(1)” with “O(1) per operation.” In a dynamic array, push is amortized O(1), but a resizing push is O(n). A second mistake is growing the capacity by a fixed amount, which raises even the amortized cost to O(n). Grow by a factor (for example ×2).
7.2.7The linked-list stack
The second implementation keeps items in a chain of linked nodes. Each node has two fields: the item’s value and a reference to the next node.
class Node {
int data;
Node next;
Node(int data) { this.data = data; } // next = null
}In Node, data is the value and next refers to the node below. The constructor sets only data; unassigned reference fields default to null in Java, so a new node’s next is null.
top points to 30, 30 to 20, and 20 to 10, whose next is null. push and pop work only on the head (top) node.Properties of this implementation:
- Flexible size. Each
pushallocates a node, so the size adapts at run time; there is no overflow until memory runs out. - Extra memory. Each node stores a
nextreference besides its value. For small items such as anint, the reference can take as much space as the item or more. - No random access, but a fast top. Reaching the kth item takes k steps from the head. A stack, though, needs only its top, which is the head, and the
topreference points straight at it. - Insertion and removal in O(1). Both happen at the head and change only a few references.
Why the head and not the tail? In a singly linked list each node knows only its successor. Deleting the last node means setting the second-to-last node’s next to null, and reaching that node requires walking from the head: O(n). Deleting the head merely advances the head reference: O(1). With the top at the head, both operations are constant-time.
7.2.8Code trace: push on a linked list
The linked stack’s fields and push method:
Node top; // top of the stack
int size;
public void push(int data) {
Node temp = new Node(data);
temp.next = top;
top = temp;
size++;
}top refers to the top node and size counts the items; an empty stack has top = null and size = 0. push takes four steps: create the node, held by the local variable temp; link its next to the old top; point top at it; increment size. We trace push(10) and then push(15) on an empty stack.
| Line | temp | top | Chain | size |
|---|---|---|---|---|
| (start) | — | null | null | 0 |
temp = new Node(10) | node(10), next = null | null | null | 0 |
temp.next = top | node(10) → null | null | null | 0 |
top = temp | node(10) | node(10) | 10 → null | 0 |
size++ | (method returns) | node(10) | 10 → null | 1 |
temp = new Node(15) | node(15), next = null | node(10) | 10 → null | 1 |
temp.next = top | node(15) → node(10) | node(10) | 10 → null | 1 |
top = temp | node(15) | node(15) | 15 → 10 → null | 1 |
size++ | (method returns) | node(15) | 15 → 10 → null | 2 |
On the first push(10), top is null, so temp.next = top leaves next null and the node stands alone. When the method returns, the local temp vanishes but the node survives, now reachable through top.
On the second push, node 15 is not yet in the list when created; top still points to 10. temp.next = top links 15 to the old top, and only then does top = temp move the top. Nothing is shifted and just two references change, so push is O(1).
push(15). (a) Node 15 is created and temp points to it; top is still at 10. (b) temp.next = top: 15 is linked to 10. (c) top = temp: 15 is the new top. (d) The method has returned: top to bottom, 15 → 10.Swapping the two lines in push, so that top = temp runs before temp.next = top. Then top already refers to the new node, and the second line points the node’s next at itself. The old list, no longer referenced, is lost, and the stack becomes one node pointing to itself. Always link the new node to the old list first, then move the top.
7.2.9Code trace: pop on a linked list
The pop method, and a loop calling it three times:
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());If top == null, pop throws RuntimeException("empty"). Otherwise it saves the top value in result, advances top to the node below, decrements size, and returns the value. The value must be saved before the node is unlinked: once top moves, the old top node is unreachable.
| Call | top == null? | result | Chain (after) | size | Printed |
|---|---|---|---|---|---|
1st pop() | no | 15 | 10 → null | 1 | 15 |
2nd pop() | no | 10 | null | 0 | 10 |
3rd pop() | yes | — | null | 0 | exception: “empty” |
On the first call, top = top.next moves the top down to node 10. Nothing refers to the old node 15 any more, and the garbage collector will reclaim it. No explicit null is needed, unlike in the array stack, because top was the node’s only reference and it has moved on. The second call sets top to null, emptying the stack. The third finds the stack empty and throws, and since nothing catches the exception, the program stops.
pop(). (a) result = top.data: 15 is saved. (b) top = top.next: the top moves to 10; the faded node 15 is unreferenced. (c) On the second pop, top becomes null; the stack is empty. (d) The third pop finds an empty stack: underflow exception.Without the emptiness check, top.data would dereference null on the third call and throw NullPointerException. The program stops either way, but a descriptive exception names the cause, not just a symptom.
7.2.10Array or linked list?
Both implementations perform the basic operations in O(1), so the choice rests on secondary criteria such as memory use and capacity behavior.
| Criterion | Array-based | Linked-list-based |
|---|---|---|
| Size | fixed capacity (grown in a dynamic array) | flexible, changes at run time |
push / pop | O(1) (amortized O(1) with resizing) | O(1) |
| Overflow | possible | only when memory runs out |
| Memory per item | data only | data + next reference |
| Wasted space | unused capacity | none |
| Java | ArrayDeque, Stack (based on Vector) | LinkedList (as a Deque) |
The table shows a trade-off. The array uses less memory per item, but its unused capacity may be wasted; the linked list keeps no spare capacity but carries a reference per item. A dynamic array occasionally copies O(n) items in one push, while a linked list allocates an object on every push, a constant but not negligible cost. Thanks to the cache, the array-based stack is usually faster in practice.
For new Java code, the recommended stack is the Deque interface with the ArrayDeque class:
Deque<Integer> s = new ArrayDeque<>();
It provides push, pop, and peek. A Deque (double-ended queue) allows insertion and removal at both ends; as a stack it uses only the front. The old java.util.Stack extends Vector, whose methods are all synchronized: each call takes a lock against simultaneous access by several threads, a needless slowdown in single-threaded programs. Inheritance from Vector also exposes operations foreign to a stack, such as inserting at an arbitrary index. The rest of these notes therefore use ArrayDeque.
7.3Stack applications
The problems in this section look unrelated: browser history, function calls, parentheses, number sequences. Yet each asks one question, “Which item, seen most recently, is still unresolved?”, and a stack answers it in constant time.
7.3.1Where stacks are used
Stacks appear throughout computer science:
- Expressions. Converting between infix, prefix, and postfix, evaluating postfix, and checking parentheses (7.5 and 7.3.5).
- Undo / redo in text editors.
- Browser history. “Back” and “forward” are managed with two stacks.
- The call stack. Function calls, local variables, and return addresses.
- Backtracking. In maze solving, n-queens, or the Knight’s Tour, returning to the latest decision point after a dead end.
- Algorithms. Towers of Hanoi, tree traversal, stock span, histogram problems.
- Graph algorithms. Depth-first search (DFS), topological sorting, strongly connected components.
- String reversal. Characters go in in order and come out reversed.
- Next greater element. For each item of an array, the first larger value to its right.
In each, jobs start nested and finish in reverse order: the latest decision is undone first, the last bracket opened closes first, the last method called returns first. Such nesting is exactly what LIFO models.
7.3.2Back and forward in a browser: two stacks
A browser runs “back” and “forward” on two stacks. The back stack holds pages visited before the current one, the forward stack pages left by going back; the current page sits in its own variable. The operations:
visit(s): push the current page onto the back stack, clear the forward stack, and makescurrent.back(): push the current page onto the forward stack; the page popped from the back stack becomes current.forward(): push the current page onto the back stack; the page popped from the forward stack becomes current.
The browser opens on “Home,” followed by visit(News), visit(Sports), back(), back(), forward(), visit(Weather), back(). The table gives the state after each step (stacks written bottom to top).
| Operation | Back stack | Current page | Forward stack |
|---|---|---|---|
| (open) | [] | Home | [] |
visit(News) | [Home] | News | [] |
visit(Sports) | [Home, News] | Sports | [] |
back() | [Home] | News | [Sports] |
back() | [] | Home | [Sports, News] |
forward() | [Home] | News | [Sports] |
visit(Weather) | [Home, News] | Weather | [] (Sports deleted) |
back() | [Home] | News | [Weather] |
The key step is visit(Weather). Sports is on the forward stack, but a new link from News starts a new path, so Sports is no longer “forward,” and the forward stack is cleared.
back() calls: the back stack is empty, the current page is Home, and the forward stack holds Sports and News, bottom to top. Bottom, after visit(Weather): the back stack holds Home and News, and the forward stack has been cleared (Sports deleted).Every operation is O(1) except clearing the forward stack, and even that is O(1) if we swap in a fresh empty stack. Undo/redo works the same way: the undo stack holds changes, the redo stack undone ones, and a new change clears the redo stack.
Forgetting to clear the forward stack on a new visit. “Forward” from Weather would then lead to Sports, on an abandoned path.
7.3.3The call stack
Every method call gets a record in memory holding its parameters, its local variables, and where execution resumes afterward (the return address). This stack frame lives on the call stack: a call pushes it, a return pops it, and control passes to the caller, now on top.
A method that calls itself, which is called recursion, shows the call stack most clearly. This one computes n! = n · (n − 1) · … · 1.
int fact(int n) {
if (n <= 1)
return 1;
return n * fact(n - 1);
}
// in main:
System.out.println(fact(3));The case n <= 1, answered directly without a recursive call, is the base case. Every other case computes n * fact(n - 1), and the multiplication must wait until fact(n - 1) finishes.
| Step | Event | Call stack |
|---|---|---|
| 1 | main runs | main |
| 2 | fact(3) called; 3 <= 1 false; 3 * fact(2) waits | main, fact(3) |
| 3 | fact(2) called; 2 <= 1 false; 2 * fact(1) waits | main, fact(3), fact(2) |
| 4 | fact(1) called; 1 <= 1 true; return 1 | main, fact(3), fact(2), fact(1) |
| 5 | fact(1) popped, returned 1; fact(2): 2 · 1 = 2 | main, fact(3), fact(2) |
| 6 | fact(2) popped, returned 2; fact(3): 3 · 2 = 6 | main, fact(3) |
| 7 | fact(3) popped, returned 6; main prints 6 | main |
The stack grows in steps 2–4 and shrinks in steps 5–7; this shrinking after the base case is called unwinding. Each suspended frame resumes when the frame above it returns: fact(2) finishes its multiplication with the 1 from fact(1).
fact(3). (a) fact(3) is suspended: 3 * fact(2) cannot be computed yet. (b) Base case: fact(1) is on top and will return 1. (c) fact(2) has been popped, returning 2; fact(3) computes 3 · 2. (d) fact(3) has returned 6; only main remains.The call stack is at most n frames deep, so fact(n) uses O(n) memory. Its size is limited: very deep recursion, or recursion that never reaches its base case, fills it, and Java throws StackOverflowError, an overflow of the call stack.
Omitting the base case, or making it unreachable (calling fact(n + 1), say). Every call pushes a frame and none returns, until the program dies with StackOverflowError.
7.3.4Reversing a string
Items pushed in order come out reversed, which is all we need to reverse a string.
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"));The stack is a Deque<Character> because Java generics take wrapper classes (Character), not primitives (char); push(c) converts automatically. toCharArray() copies the characters into a new array, and since a String is immutable, the result is built by overwriting that array.
The first loop pushes the characters; the second refills the array from i = 0 with popped ones. For "ABCD" the stack becomes A, B, C, D bottom to top; the first pop gives D, so chars[0] = 'D', then C, B, A follow. The result is "DCBA".
reverse("ABCD"). (a) After the first loop: the stack is A, B, C, D bottom to top, with the last character on top. (b) Second loop, i = 2: D and C are in place, and B is popped into cell 2.Each character is pushed and popped once: O(n) time, and the stack and array need O(n) extra memory. The same idea tests for a palindrome, a word that reads the same backward (“kayak”): compare the reversed string with the original.
7.3.5Balanced parentheses: the problem and the algorithm
One of a compiler’s first jobs is checking that brackets match. Simplified, let the string S consist only of ( ) { } [ ].
A string S over the characters ( ) { } [ ] is valid if and only if:
- every opening bracket is closed by a closing bracket of the same type;
- brackets are closed in the right order: the last one opened closes first.
This check is called balanced parentheses.
The second condition is the LIFO rule itself, so the algorithm keeps the unmatched opening brackets on a stack and scans S once:
- An opening bracket is pushed to await its closing partner.
- For a closing bracket: if the stack is empty,
Sis invalid (nothing to close). Otherwise pop; if the popped bracket is not of the same type,Sis invalid. - At the end of
S, it is valid if the stack is empty and invalid otherwise (some bracket was never closed).
The kinds of error it catches:
| String | Result | Why? |
|---|---|---|
{[()]} | valid | each bracket closes in reverse order |
([)] | invalid | wrong order: when ) arrives, the latest open is [ |
(() | invalid | unclosed: ( remains on the stack |
()) | invalid | extra closer: the last ) meets an empty stack |
([)] shows why counting is not enough: one opener and one closer of each type, so the counts balance, but the order is wrong. A counter misses this; a stack does not.
7.3.6Code: isBalanced()
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();
}A helper method says whether an opener and a closer are of the same type:
boolean matches(char open, char close) {
return (open == '(' && close == ')')
|| (open == '[' && close == ']')
|| (open == '{' && close == '}');
}isBalanced pushes each opener. For a closer it returns false if the stack is empty; otherwise it pops the latest opener into open and returns false if matches(open, c) fails. Finally, return stack.isEmpty(); reports whether any bracket was left unclosed.
matches joins the three valid pairs with || (or); && (and) requires both halves of a pair. For matches('[', ')') no pair holds: false.
Each character costs O(1): O(n) time. At worst (all openers) the stack holds n items: O(n) memory.
Skipping the empty-stack check for a closer. Without if (stack.isEmpty()) return false;, input like "())" or "]" pops an empty ArrayDeque, which throws NoSuchElementException. Another typical mistake is returning true after the loop, which accepts unclosed strings such as "(()".
7.3.7Code trace: a valid string
We trace isBalanced("{{[]}()}[]"). The string has ten characters, at indices 0 to 9.
| Index | c | Action | Stack (after) |
|---|---|---|---|
| 0 | { | push | { |
| 1 | { | push | { { |
| 2 | [ | push | { { [ |
| 3 | ] | pop [; matches('[', ']') true | { { |
| 4 | } | pop {; matches('{', '}') true | { |
| 5 | ( | push | { ( |
| 6 | ) | pop (; matches('(', ')') true | { |
| 7 | } | pop {; matches('{', '}') true | empty |
| 8 | [ | push | [ |
| 9 | ] | pop [; matches('[', ']') true | empty |
| (end) | — | return stack.isEmpty() | empty → true |
Whenever a closer arrives, the nearest open opener is on top: the } at index 4 matches the { at index 1, the one at index 7 matches index 0, found by LIFO alone with no searching. The stack is briefly empty after index 7, so that prefix is valid on its own. At the end it is empty, and the method returns true.
isBalanced("{{[]}()}[]"). (a) Index 2: [ pushed; the stack is { { [. (b) Index 3: ] arrives and the latest opener, [, comes off. (c) Index 5: ( pushed; the stack is { (. (d) End of string, stack empty: true.7.3.8Invalid strings
The algorithm returns false in three places, each for a different error.
Wrong order: "([)]".
(is pushed; stack: (.[is pushed; stack: ( [.)arrives and the pop yields the latest opener,[.matches('[', ')')is false, so the method returnsfalsewithout reading the final].
Unclosed: "(()". Two ( are pushed and ) removes one. The loop ends with a ( on the stack; stack.isEmpty() is false, so false is returned.
Extra closer: "())". ( is pushed and the first ) removes it, emptying the stack. When the second ) arrives, the empty check returns false.
isBalanced("([)]"). (a) Two openers pushed: the stack is ( [. (b) ) arrives and the pop yields [; the types do not match, and the method returns false.Returning early is a bonus: once an error is found, the rest need not be read. The worst case stays O(n), but invalid input often finishes much sooner.
The same algorithm checks that HTML tags such as <b><i>…</i></b> nest properly: push each opening tag, pop on each closing tag, and compare names.
7.3.9Next greater element
Given an array of numbers, find for each item the first item to its right that is larger, or −1 if there is none. This is the next greater element problem.
| index | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| arr | 4 | 7 | 3 | 4 | 8 | 1 |
| result | 7 | 8 | 4 | 8 | −1 | −1 |
By hand: right of 4, the first larger item is 7. Right of 7, 3 and 4 are smaller, so the answer is 8. The 4 next to 3 is larger. Right of 8 there is only 1: −1. The last item has nothing to its right: −1.
Brute force scans right from each item to the first larger one. On a decreasing array every scan runs to the end: (n − 1) + (n − 2) + … + 1 = n(n − 1)/2 comparisons, O(n2) time.
A stack does it in O(n). Scan right to left, keeping only “candidates” on the stack: values that could still answer for items further left. When x arrives, pop every value on top that is at most x. These lie right of x and are no larger; any item left of x meets x first, and if x is not big enough, neither are they, so they can never answer again. The first value not popped, if any, is the answer for x, which is then pushed as a candidate.
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;
}The stack therefore stays decreasing bottom to top, since each item is pushed only after everything at most its size is gone. A stack whose contents stay sorted is a monotonic stack.
On an array of n items, nextGreater runs in O(n) time and uses O(n) extra memory.
The loops are nested, but the time is not O(n2). Each turn of the inner while pops once. Every item is pushed exactly once (last line of the outer loop) and never returns once removed, so it is popped at most once: at most n pops over the whole run. The outer loop runs n times with constant work besides the inner loop. The total is on the order of n + n = 2n, so O(n). The stack holds at most n items and result has n cells: O(n) memory. □
This is amortized analysis again: one outer turn may run the inner loop many times, but the total is bounded. Nested loops do not automatically mean quadratic time; count the inner loop’s total runs.
7.3.10Code trace: nextGreater()
i | arr[i] | popped | result[i] | Stack (after push) |
|---|---|---|---|---|
| 5 | 1 | — | −1 (stack empty) | 1 |
| 4 | 8 | 1 (1 ≤ 8) | −1 (stack empty) | 8 |
| 3 | 4 | — | 8 | 8 4 |
| 2 | 3 | — | 4 | 8 4 3 |
| 1 | 7 | 3, 4 (≤ 7) | 8 | 8 7 |
| 0 | 4 | — | 7 | 8 7 4 |
Initially result is all zeros (Java zero-fills a new int array) and the stack is empty. At i = 5 the answer is −1, and 1 becomes a candidate. At i = 4 the 1 on top is popped: it cannot answer for anything left of 8, since 8 is larger and nearer. The stack empties; 8’s answer is −1.
Row i = 1 shows the method best. With 8, 4, 3 on the stack, the incoming 7 pops 3 and 4 and stops at 8, since 8 <= 7 is false; the answer is 8, and 3 and 4 are useless to anything left of 7. At i = 0 the 7 on top beats 4 at once. The method returns [7, 8, 4, 8, −1, −1] after six pushes and three pops, as Theorem 7.2 allows.
nextGreater trace. (a) i = 4: 1 ≤ 8 on top, popped. (b) i = 1: 3 ≤ 7 on top, popped. (c) i = 1: 4 is gone too, leaving 8 on top; result[1] = 8. (d) The result array is complete: [7, 8, 4, 8, −1, −1].Writing the inner comparison as <. With stack.peek() < arr[i], equal values stay, and an item’s “next greater” may be equal to it: in {5, 5} the first 5 should get −1, but gets 5. Equal is “not greater,” so use <=.
7.3.11A stack that returns its minimum in O(1)
Now add getMin(), returning the smallest value on the stack. Scanning takes O(n); we want O(1), like the other operations.
The solution is a second stack. s holds all items; the top of min is always the smallest value in s. A value no larger than the current minimum also goes onto min, and a value leaving s that equals the top of min leaves min too, restoring the previous minimum.
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(); }| Operation | Condition | s | min | Returns |
|---|---|---|---|---|
push(5) | min empty → push | 5 | 5 | — |
push(3) | 3 <= 5 true → push | 5 3 | 5 3 | — |
push(7) | 7 <= 3 false | 5 3 7 | 5 3 | — |
push(3) | 3 <= 3 true → push | 5 3 7 3 | 5 3 3 | — |
getMin() | — | 5 3 7 3 | 5 3 3 | 3 |
pop() | 3 == 3 → also pop min | 5 3 7 | 5 3 | 3; getMin() = 3 |
pop() | 7 == 3 false | 5 3 | 5 3 | 7; getMin() = 3 |
pop() | 3 == 3 → also pop min | 5 | 5 | 3; getMin() = 5 |
Each operation does at most two pushes, two pops, or a peek, all O(1); getMin() is just min.peek(). The price is memory: with pushes in decreasing order, min also holds n items, O(n) extra.
push(3): since 3 <= 3, it also goes onto min, which is now 5, 3, 3 bottom to top. (b) After three pop calls only 5 remains in s; getMin() = 5.Writing the push condition as x < min.peek(). Equal minima then enter min once: above, the second 3 stays out, so the first pop also removes the only 3 in min, and although s still holds a 3, getMin() returns 5. With <=, each equal minimum gets its own copy.
The x == min.peek() test in pop is subtle too. x is an int and min.peek() an Integer, so Java unboxes and compares values, correctly. Were both sides Integer, == would compare object identity and could fail for values above 127; then use equals.
7.4Towers of Hanoi and stock span
Two more uses of a stack. In the Towers of Hanoi it makes recursion’s hidden call stack explicit; in stock span it keeps “candidates,” as in next greater element, turning a quadratic solution into a linear one.
7.4.1Towers of Hanoi without recursion
The puzzle has three pegs (A, B, C) and n disks of different sizes, stacked on A largest at the bottom, to be moved in the same order to C. Each move takes one top disk, and a larger disk may never go on a smaller one. Each peg is a stack: only its top disk is accessible.
The classic solution is recursive. To move n disks from A to C using B:
- move the top n − 1 disks from A to B (using C);
- move the largest disk from A to C;
- move the n − 1 disks from B to C (using A).
A single disk (the base case) moves directly. Recursively, the call stack holds the pending subtasks: while step 1 runs, steps 2 and 3 wait. An explicit stack can store each pending job as a task instead.
// Task: move n disks from peg 'from' to peg 'to' via '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 { // in 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()));
}
}
}A record (Java 16) is a compact class: Task is an immutable object with four fields, read through methods such as t.n() and t.from(): the disk count n and the source, auxiliary, and destination pegs from, via, to. (3, A, B, C) means “move 3 disks from A to C using B.”
The method pushes the initial task and pops tasks until the stack is empty. A one-disk task is done at once by printing the move. Any other is split into the three subtasks above, pushed in reverse order: step 3 (n−1, via, from, to), step 2 (1, from, via, to), then step 1 (n−1, from, to, via), which thus lands on top and comes off next.
The reverse order is LIFO at work. In natural order, step 3 would run first, moving disks from B to C that have not yet left A.
7.4.2Code trace: hanoi(3)
Table 7.15 shows every iteration of hanoi(3). The stack column lists tasks (n,from,via,to) top to bottom, so the first comes off next.
| Iteration | pop | Action | Stack (after the iteration, top to bottom) |
|---|---|---|---|
| 0 | — | initial task | (3,A,B,C) |
| 1 | (3,A,B,C) | push 3 subtasks | (2,A,C,B) (1,A,B,C) (2,B,A,C) |
| 2 | (2,A,C,B) | push 3 subtasks | (1,A,B,C) (1,A,C,B) (1,C,A,B) (1,A,B,C) (2,B,A,C) |
| 3 | (1,A,B,C) | move 1: A → C | (1,A,C,B) (1,C,A,B) (1,A,B,C) (2,B,A,C) |
| 4 | (1,A,C,B) | move 2: A → B | (1,C,A,B) (1,A,B,C) (2,B,A,C) |
| 5 | (1,C,A,B) | move 3: C → B | (1,A,B,C) (2,B,A,C) |
| 6 | (1,A,B,C) | move 4: A → C | (2,B,A,C) |
| 7 | (2,B,A,C) | push 3 subtasks | (1,B,C,A) (1,B,A,C) (1,A,B,C) |
| 8 | (1,B,C,A) | move 5: B → A | (1,B,A,C) (1,A,B,C) |
| 9 | (1,B,A,C) | move 6: B → C | (1,A,B,C) |
| 10 | (1,A,B,C) | move 7: A → C | empty |
In iteration 1, with t.n()-1 = 2, t.via() = B, t.from() = A, t.to() = C, the pushes are (2,B,A,C), (1,A,B,C), (2,A,C,B), leaving “move 2 disks from A to B” on top. Iteration 2 splits it into three one-disk tasks, and iterations 3–5 move the two smallest disks to B (A → C, A → B, C → B). Iteration 6 moves the largest disk from A to C, step 2 of the first split. Iteration 7 splits the remaining “move 2 disks from B to C,” and 8–10 finish it.
There are 7 = 23 − 1 moves; in general n disks need 2n − 1, since T(n) = 2T(n − 1) + 1 and T(1) = 1. Slightly more tasks are popped: one per move plus the split ones, so for n = 3, 7 + 3 = 10 iterations. The time is O(2n), a lower bound for the problem itself, since every move must be printed.
Memory is far better: the trace never had more than 5 tasks. Each split replaces one task with three, growing the stack by two, and at most n − 1 splits can stack up (each lowers the disk count). So the stack holds at most 1 + 2(n − 1) = 2n − 1 tasks, O(n): 5 for n = 3, 7 for n = 4.
An explicit stack puts pending work under the program’s control. The runtime limits the call stack, so deep recursion throws StackOverflowError, while an explicit stack grows on the heap. The same transformation works for other recursive algorithms, such as depth-first search.
Pushing subtasks in the order they should run. A stack pops the last item first, so the first task must be pushed last: when converting recursion to an explicit stack, push subcalls in reverse order.
7.4.3The stock span problem
Given a stock’s daily closing prices, financial analysts often ask how many days today’s price has gone unexceeded: the stock span problem.
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. The count stops at the first day with a higher price.
Note three details: every span is at least 1, since today counts; equal prices count; and days must be consecutive, so the count ends at the first higher price even if lower ones lie beyond.
Let the prices over 7 days be [100, 80, 60, 70, 60, 75, 85], with days numbered 1 to 7.
- Day 1 (100): no earlier day. Span 1.
- Day 2 (80): the day before has 100 > 80; stop. Span 1.
- Day 3 (60): the day before has 80 > 60. Span 1.
- Day 4 (70): day 3’s 60 ≤ 70 counts; day 2’s 80 > 70 stops. Span 2.
- Day 5 (60): day 4’s 70 > 60. Span 1.
- Day 6 (75): 60, 70, 60 ≤ 75 count; day 2’s 80 > 75 stops. Span 4.
- Day 7 (85): 75, 60, 70, 60, 80 ≤ 85 count; day 1’s 100 > 85 stops. Span 6.
Result: [1, 1, 1, 2, 1, 4, 6].
Brute force walks back from each day to a higher price. With steadily rising prices every walk reaches the start: 1 + 2 + … + n steps, O(n2). A stack removes the repeated walking.
7.4.4Code: span()
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;
}The idea is next greater element’s, but scanning left to right with indices on the stack: those of days not yet exceeded, with prices decreasing bottom to top. When day i arrives, days priced at or below today are popped. They can never limit a future day: today has exceeded them and is a nearer, higher (or equal) barrier.
The top is then the latest earlier day with a higher price, and the span is the distance to it, i - stack.peek(). If the stack is empty, today beat every earlier price and the span is i + 1 (days 0 to i). Finally today is pushed as a possible future barrier.
The first day, with span 1, is handled before the loop by stack.push(0); spans[0] = 1;, so the loop starts at i = 1. Since the stack holds indices, the condition prices[i] >= prices[stack.peek()] reaches prices through them.
The cost argument is that of next greater element: each index is pushed once and popped at most once, so time and memory are O(n).
7.4.5Code trace: span()
i | Price | popped | Top | spans[i] | Stack (after push) |
|---|---|---|---|---|---|
| 0 | 100 | — | — | 1 (first day) | 0(100) |
| 1 | 80 | — | 0 | 1 − 0 = 1 | 0(100) 1(80) |
| 2 | 60 | — | 1 | 2 − 1 = 1 | 0(100) 1(80) 2(60) |
| 3 | 70 | 2 (60 ≤ 70) | 1 | 3 − 1 = 2 | 0(100) 1(80) 3(70) |
| 4 | 60 | — | 3 | 4 − 3 = 1 | 0(100) 1(80) 3(70) 4(60) |
| 5 | 75 | 4, 3 | 1 | 5 − 1 = 4 | 0(100) 1(80) 5(75) |
| 6 | 85 | 5, 1 | 0 | 6 − 0 = 6 | 0(100) 6(85) |
At i = 3, 70 beats day 2 (60), which is popped: day 3 (70) is nearer and higher for any later day. Day 1 (80) is on top, so the span is 3 − 1 = 2. At i = 5, 75 pops days 4 (60) and 3 (70) and stops at day 1 (80): span 5 − 1 = 4. At i = 6, 85 pops days 5 (75) and 1 (80) and stops at day 0 (100): span 6. The result [1, 1, 1, 2, 1, 4, 6] matches Example 7.5, after 7 pushes and 5 pops.
span trace. (a) i = 3: 70 ≥ 60, day 2 is popped. (b) i = 5: days 3 and 4 are gone, day 1 (80) is on top; span = 4. (c) i = 6: 85 ≥ 80, day 1 is popped too. (d) End: the stack holds 0(100) and 6(85); the spans are [1, 1, 1, 2, 1, 4, 6].Taking the span as i when the stack empties; days are numbered from 0, so day i ends a run of i + 1 days. Another typical mistake is using > in the inner loop, which leaves equal-priced days uncounted, though the definition says “less than or equal.”
7.5Expression notations
An expression like 3 + 4 * 2 is easy for people but hard for a computer in two ways: the multiplication is done before the addition written to its left, and parentheses, possibly nested, change the order. This section writes expressions in two forms that make the order explicit, and converts between them with a stack.
7.5.1Infix, prefix, and postfix
An expression has operands (A, B, 5) and operators (+, −, *, /). The three notations differ in where the operator goes.
| Notation | Operator position | A * (B + C) / D |
|---|---|---|
| Infix | between the operands | A * ( B + C ) / D |
| Prefix · Polish notation | before the operands | / * A + B C D |
| Postfix · reverse Polish notation | after the operands | A B C + * D / |
Everyday notation is infix, as in B + C. In prefix the operator comes first, + B C; this is Polish notation, after the logician Jan Łukasiewicz, who proposed it in the 1920s. In postfix it comes last, B C +: reverse Polish notation, or RPN.
To read postfix, scan A B C + * D / from the left; at each operator, the two values just before it are its operands and are replaced by the result. B C + → (B + C); then A (B + C) * → A * (B + C); then … D / → (A * (B + C)) / D.
Prefix and postfix need neither parentheses nor precedence rules: the order of operations follows from the sequence of symbols. Postfix is evaluated left to right, prefix right to left, in one pass with a stack, which is why compilers and interpreters usually convert to postfix. Java bytecode is stack-based: a + b becomes “push a, push b, add.” Some calculators (older HP models) work in RPN, as do symbolic algebra programs and expression-tree algorithms.
Moving operators forward in their infix order to get prefix. For A * (B + C) / D, * A / + B C D is wrong: it means A * ((B + C) / D), which can differ under integer division and is a different computation tree anyway. Infix applies * and / left to right, A * (B + C) first, so the correct prefix is / * A + B C D, with the outermost operation first.
7.5.2Precedence, associativity, and converting by hand
Infix needs two rules: precedence says which of two different operators goes first, and associativity in which direction equal-precedence operators apply.
| Operator | Precedence | Associativity |
|---|---|---|
^ (power) | 3 | right to left |
* / | 2 | left to right |
+ − | 1 | left to right |
Left associativity makes A − B + C mean (A − B) + C: 10 − 4 + 3 is 6 + 3 = 9, whereas grouping from the right gives 10 − 7 = 3. Its postfix is A B − C +. Exponentiation is right-associative: 2 ^ 3 ^ 2 is 2 ^ (3 ^ 2) = 29 = 512, not (2 ^ 3) ^ 2 = 64.
To convert by hand, use full parenthesization:
- Following precedence and associativity, put every operation in its own parentheses.
- For postfix, move each operator to the right end of its parentheses and delete the parentheses.
- For prefix, move each operator to the left end of its parentheses and delete the parentheses.
(a) A * (B + C) / D. The parentheses first, then * and / left to right: ((A * (B + C)) / D).
- Postfix:
(B + C)→B C +;(A * …)→A B C + *;(… / D)→A B C + * D /. - Prefix:
(B + C)→+ B C;(A * …)→* A + B C;(… / D)→/ * A + B C D.
(b) A + B * C − D / E. * and / first, then + and − left to right: ((A + (B * C)) − (D / E)).
- Postfix: left side
A B C * +, right sideD E /, and−last:A B C * + D E / −. - Prefix:
−first, then the left side+ A * B C, then the right side/ D E:− + A * B C / D E.
A quick check: operands keep the same order in all three notations; only operators move. A conversion that scrambles the operands is certainly wrong. The converse fails: operands can be in order with operators misplaced (like * A / + B C D in 7.5.1).
7.5.3Infix to postfix: the algorithm
A program would have to parse the whole expression to parenthesize it. Edsger Dijkstra’s 1961 shunting-yard algorithm, named after a railway switching yard, instead makes one left-to-right pass: operators and opening parentheses wait on a stack, operands go straight to the output.
| Symbol read | Action |
|---|---|
| Operand (A, B, 5 …) | Write it to the output. |
( | Push it. |
) | Pop and output until ( appears; discard the (. |
Operator op | While the top is not ( and its precedence is ≥ that of op, pop and output; then push op. |
| End of input | Pop and output all remaining operators. |
The reasoning: a newly read operator still lacks its right operand, so it waits on the stack. When op arrives, waiting operators of precedence at least op’s have complete operands and must be applied first, so they are output; lower ones apply after op and keep waiting.
Popping on equality (≥) reflects left associativity: in A − B + C, when + arrives, the equal − must go first and is output, giving A B − C +. For right-associative ^, only strictly greater precedence (>) pops, so in A ^ B ^ C the second ^ goes above the first and applies first.
A parenthesis walls off precedence: operators pushed after ( never look beneath it, and ) pops only down to it. Parentheses are never output; postfix needs none.
Each symbol is read once and each operator pushed and popped once: O(n) time for an expression of length n, and O(n) stack memory at worst.
7.5.4Code: infixToPostfix()
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();
}The helper that gives the precedences:
int prec(char op) {
switch (op) {
case '+': case '-': return 1;
case '*': case '/': return 2;
default: return 0; // '('
}
}The code applies the five rules of Table 7.19 in order. A letter or digit (Character.isLetterOrDigit(c)) is an operand and is appended to out, a StringBuilder, Java’s efficient way to build a string piece by piece. A ( is pushed. On ), while (stack.peek() != '(') outputs the operators down to the (, and one more stack.pop() discards it. Anything else is an operator: the while loop pops while the top’s precedence is not lower than the incoming one’s, then pushes the newcomer. At the end the remaining operators are appended.
The condition “the top is not (” is handled by prec: its default branch gives ( precedence 0, below every real operator, so prec('(') >= prec(c) is never true. Only ) pops a (.
The code assumes single-character operands, only + − * / (no ^), no spaces, and a valid expression. Given a ) with no matching (, stack.peek() returns null on the empty stack, and null != '(' throws NullPointerException while unboxing the Character.
7.5.5Code trace: A*(B+C)/D
c | Action | Stack (after) | out |
|---|---|---|---|
A | operand → output | empty | A |
* | stack empty → push | * | A |
( | always push | * ( | A |
B | operand → output | * ( | A B |
+ | ( on top (precedence 0) → push | * ( + | A B |
C | operand → output | * ( + | A B C |
) | pop to (: + to output; ( discarded | * | A B C + |
/ | top * (2) ≥ / (2) → * to output; stack empty → push / | / | A B C + * |
D | operand → output | / | A B C + * D |
| (end) | remaining / to output | empty | A B C + * D / |
The decisive moment is reading /: the ≥ rule outputs the equal-precedence *, which is on the left in infix and goes first. Had it stayed, the result would be A B C + D / *, meaning A * ((B + C) / D). The result matches the hand conversion of Example 7.6.
A*(B+C)/D. (a) + read: with ( on top, it is pushed directly. (b) ) read: + goes to the output. (c) / read: the equal-precedence * goes to the output. (d) End of input: the remaining / is output; the result is A B C + * D /.7.5.6Code trace: A+B*C-D/E
With no parentheses, precedence does all the work.
c | Action | Stack (after) | out |
|---|---|---|---|
A | to output | empty | A |
+ | stack empty → push | + | A |
B | to output | + | A B |
* | top + (1) < 2 → push | + * | A B |
C | to output | + * | A B C |
- | * (2) ≥ 1 → output; + (1) ≥ 1 → output; push - | − | A B C * + |
D | to output | − | A B C * + D |
/ | top - (1) < 2 → push | − / | A B C * + D |
E | to output | − / | A B C * + D E |
| (end) | /, then - to output | empty | A B C * + D E / − |
At *, the lower-precedence + keeps waiting and * goes on top: the stack’s version of “multiplication first.” At -, two operators leave: the higher *, then the equal +. At the end the stack empties from the top, / then −. The result A B C * + D E / − means (A + B·C) − D/E.
A+B*C-D/E. (a) * sits on the lower-precedence +; output A B C. (b) - arrives: * has been output, and now the equal-precedence + leaves. (c) / is on -, and E has been output. (d) End: the stack is emptied; the result is A B C * + D E / −.Popping only one operator when an operator arrives. The rule is a while loop. In A+B*C-D/E, popping only * at - and leaving + would give A B C * D E / − +, meaning A + (B·C − D/E), which breaks left associativity.
7.5.7Evaluating a postfix expression
The payoff of postfix is trivial evaluation. Read left to right:
- Push each number.
- On an operator, pop two items, the right operand first, then the left; apply the operator and push the result.
- At the end, one item remains on the stack: the result.
No parentheses or precedence are involved; each symbol is handled once: O(n).
A helper does the arithmetic:
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;
}
}The evaluation method:
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();
}c - '0' turns a digit character into its value: characters are numeric codes, and '0' through '9' are consecutive, so '7' - '0' is 7. On an operator, the first pop goes into right, the second into left, and apply(left, c, right) is pushed. A final pop returns the result. The code assumes single-digit numbers, no spaces, and a valid expression; / is integer division, so 7 / 2 is 3.
Why right first? The left operand was read, and pushed, earlier, so it sits lower. For addition and multiplication this is irrelevant, but not for subtraction and division: in 5 2 −, the first pop gives 2 (right), the second 5 (left), and the result is 5 − 2 = 3, not 2 − 5 = −3.
Taking the first pop as the left operand. With int left = stack.pop(); int right = stack.pop();, every subtraction and division comes out backward. The first pop is the right operand, the second the left.
7.5.8Code trace: evaluatePostfix()
Substituting A = 4, B = 1, C = 2, D = 3 into A B C + * D / from Example 7.6 gives the postfix string "412+*3/", whose infix equivalent is 4 · (1 + 2) / 3 = 4.
c | Action | Stack (after) |
|---|---|---|
4 | digit → push 4 | 4 |
1 | digit → push 1 | 4 1 |
2 | digit → push 2 | 4 1 2 |
+ | right = 2, left = 1; apply(1, '+', 2) = 3 → push | 4 3 |
* | right = 3, left = 4; apply(4, '*', 3) = 12 → push | 12 |
3 | digit → push 3 | 12 3 |
/ | right = 3, left = 12; apply(12, '/', 3) = 4 → push | 4 |
| (end) | return stack.pop() | empty → result 4 |
The stack holds intermediate results not yet used by an operator; at +, 1 and 2 are replaced by 3. The stack sizes (1, 2, 3, 2, 1, 2, 1) also reveal validity: each number adds one, each binary operator removes one, and in a valid expression the stack holds at least 2 items at every operator and exactly 1 at the end.
"412+*3/". (a) + arrives: right = 2 comes off, then left = 1. (b) 1 + 2 = 3 is pushed. (c) 4 · 3 = 12 is pushed. (d) 12 / 3 = 4; one value remains.7.5.9Infix to prefix
Infix to prefix needs no new algorithm, just the postfix one, slightly modified, between two reversals:
- Reverse the infix expression.
- Swap the parentheses:
(↔). - Apply the postfix algorithm, but do not pop on equal precedence: pop only operators of strictly greater precedence (>).
- Reverse the output.
| Step | Result |
|---|---|
| 1. Reverse | D / ) C + B ( * A |
| 2. Swap parentheses | D / ( C + B ) * A |
| 3. Convert to postfix (no pop on equal) | D C B + A * / |
| 4. Reverse → prefix | / * A + B C D |
Prefix is the mirror image of postfix. Reversal flips each operator’s position relative to its operands, so taking the postfix of the reversed expression and reversing it puts each operator in front. Reversal also flips the parentheses, which step 2 corrects.
The “no pop on equal” rule preserves associativity, which reversal turns from left to right. When * arrives in the reversed expression, the equal / stays and * goes above it, so * applies first, and after the final reversal A * (B + C) still precedes / D. Popping on equality would give D C B + / A * and the prefix * A / + B C D, the very mistake of 7.5.1.
For the second example, A + B * C − D / E, the same method yields − + A * B C / D E, the hand result of Example 7.6.
7.5.10Code trace: infixToPrefix()
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"));Four steps, four lines. rev uses StringBuilder’s built-in reverse(). swapParens, whose body the slides omit, swaps ( and ):
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();
}postfixForPrefix copies infixToPostfix except for the operator-branch comparison:
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());With > instead of >=, an equal-precedence operator stays. The trace for the reversed, swapped input "D/(C+B)*A":
c | Action | Stack (after) | out |
|---|---|---|---|
D | to output | empty | D |
/ | stack empty → push | / | D |
( | push | / ( | D |
C | to output | / ( | D C |
+ | ( on top → push | / ( + | D C |
B | to output | / ( + | D C B |
) | + to output, ( discarded | / | D C B + |
* | top / (2) > 2? no → push | / * | D C B + |
A | to output | / * | D C B + A |
| (end) | *, then / to output | empty | D C B + A * / |
Reversing p = "DCB+A*/" gives "/*A+BCD", which main prints; the operands are still in order A, B, C, D.
expr, the reversed t, t with parentheses swapped, p found without popping on equality, and the reversed result / * A + B C D. (b) The key moment of step 3: when * arrives, the / on top has equal precedence and is not popped; * goes on top of it.Each of the four steps (two reversals, one parenthesis swap, one conversion) is O(n), so the whole is O(n).
7.5.11Evaluating a prefix expression
Prefix evaluation follows the postfix rules with two changes: read right to left, and the first pop is the left operand, since reading from the right pushes the right operand first and the left one ends up on top.
Evaluate the prefix expression − * 3 4 / 8 2 (in infix, 3 · 4 − 8 / 2). Symbols are read right to left; the stack is written bottom to top.
2→ push. Stack: 2.8→ push. Stack: 2 8./→left= 8 (first pop),right= 2; 8 / 2 = 4 → push. Stack: 4.4→ push. Stack: 4 4.3→ push. Stack: 4 4 3.*→left= 3,right= 4; 3 · 4 = 12 → push. Stack: 4 12.−→left= 12,right= 4; 12 − 4 = 8 → push. Stack: 8.
The result is 8. Swapping the order in step 7 would give 4 − 12 = −8.
Postfix and prefix evaluation are one stack idea run in two directions: the stack holds operands awaiting an operator, and only the reading direction, hence the pop order, differs.
7.6Common mistakes and a common pattern
Most stack bugs fall into a few types. The table gathers the chapter’s mistakes into a checklist for reviewing stack code.
| Mistake | What happens? | Fix |
|---|---|---|
pop / peek on an empty stack | An exception or null, depending on the library; "())" triggers exactly this in the parentheses check. | Check isEmpty() first. |
Confusing top and size | Writing outside the array, or a wrong item count. | top is the index of the last item; size = top + 1; full means top == capacity − 1. |
| Not clearing the vacated cell | The object escapes the garbage collector (memory leak, loitering). | In an object array, data[top--] = null. |
| Operand order in postfix evaluation | Subtraction and division come out backward. | First pop is the right operand, second the left: 5 2 − = 5 − 2 = 3. |
| Iteration order | Two stacks with the same contents print in opposite orders. | ArrayDeque iterates top to bottom, the old Stack bottom to top; know which you use. |
| Infinite recursion | Frames pile up: StackOverflowError (the call stack overflows). | Every recursive method needs a reachable base case. |
Iteration order comes from library internals. java.util.Stack, a Vector, keeps items in insertion order, so toString() and for-each go bottom to top. ArrayDeque’s push inserts at the front, where iteration starts, so it goes top to bottom. After pushing 1, 2, 3, Stack prints [1, 2, 3] and ArrayDeque [3, 2, 1]: same contents, opposite output.
One pattern runs through the chapter. A stack is the right tool when the question is “Which item, seen most recently, is still unresolved?”: the latest bracket opened but not closed, the latest method called but not returned, the latest intermediate result not yet used, the nearest candidate not yet exceeded (next greater element, stock span), the latest subtask not yet done (Hanoi). When a problem raises this question, its solution very likely uses a stack.
7.7Summary
- A stack is a LIFO (last-in, first-out) ADT with insertion and removal only at the top.
push,pop,top/peek,isEmpty, andsizeare all O(1); searching is O(n); n items take O(n) memory. - Popping an empty stack is underflow; pushing onto a full bounded stack is overflow. Libraries treat empty stacks differently, so check
isEmpty()beforepopandpeek. - In an array-based stack,
topis the last item’s index (−1 when empty) andsize = top + 1; vacated object cells are set tonull. A dynamic array doubles when full: a resizingpushis O(n), but n pushes cost under 3n, sopushis amortized O(1). Growing by a fixed amount costs O(n2) in total. - In a linked-list stack the top is the head, where
pushandpopwork in O(1). It overflows only when memory runs out, and each node carries an extra reference. New Java code should useDeque<E> s = new ArrayDeque<>(). - A browser’s back/forward uses two stacks, method calls the call stack. Recursion needs memory proportional to its depth; without a base case it ends in
StackOverflowError. - Balanced parentheses keeps unmatched openers on a stack and catches three kinds of error (wrong order, unclosed, extra closer) in O(n).
- Next greater element and stock span are solved in O(n) with a monotonic stack, decreasing bottom to top, because each item is pushed once and popped at most once. A stack with O(1)
getMin()keeps the minima on a second stack (using<=). - The Towers of Hanoi yield to an explicit task stack that pushes subtasks in reverse order: 2n − 1 moves, O(n) stack.
- Infix becomes postfix in O(n) with an operator stack, popping while the top’s precedence is ≥ the incoming one’s. For prefix: reverse, swap parentheses, convert without popping on equality, reverse again. Operands keep their order in all three notations.
- Postfix evaluation reads left to right; at an operator the first
popis the right operand, the second the left. Prefix is read right to left, and the first pop is the left operand.
| Problem | On the stack | Time |
|---|---|---|
push / pop / top / isEmpty / size | — | O(1) |
| String reversal | characters | O(n) |
| Balanced parentheses | unmatched opening brackets | O(n) |
| Next greater element | candidate values to the right (decreasing) | O(n) |
| Stock span | indices of days not yet exceeded | O(n) |
| Towers of Hanoi | subtasks (n, from, via, to) | O(2n) |
| Infix → postfix / prefix | operators and ( | O(n) |
| Postfix evaluation | intermediate results | O(n) |
Min stack (getMin) | the current minimum | O(1) |
| Back / forward navigation | visited pages (two stacks) | O(1) |
7.8Key terms
Each term links to the course glossary, which gives its Turkish equivalent and its use in other chapters.
- stack
- top
- push
- pop
- LIFO (last-in, first-out)
- abstract data type (ADT)
- interface
- generic
- overflow
- underflow
- capacity
- dynamic array
- amortized analysis
- linked list
- singly linked list
- node
- reference
- undo
- call stack
- stack frame
- recursion
- base case
- backtracking
- balanced parentheses
- next greater element
- stock span
- infix
- prefix
- postfix
- depth-first search (DFS)
- time complexity
- space complexity
7.9Test Yourself
An empty stack receives
push(7),push(2),pop(),push(9),top(),push(4),pop(),pop(). What do the calls return, and what is left?Answer
The first
pop()returns 2, leaving 7. Afterpush(9),top()returns 9 without removing it. Afterpush(4)the stack is 7 9 4; the pops return 4, then 9. The values are 2, 9, 4, 9, and only 7 remains.What do
isBalanced("([]{})")andisBalanced("([]{)}")return? For the invalid one, at which character is the error detected?Answer
The first returns
true:(,[pushed;]pops[;{pushed;}pops{;)pops(; the stack ends empty. The second returnsfalse: when the)at index 4 arrives, the stack is ( { and the pop yields{;matches('{', ')')is false. The final}is never read.What are the postfix and prefix forms of
(A + B) * C − D / (E + F)?Answer
Fully parenthesized:
(((A + B) * C) − (D / (E + F))). Postfix:A B + C * D E F + / −. Prefix:− * + A B C / D + E F. The operands stay in order A to F in both. IninfixToPostfix,-pops the*, while at/the lower-precedence-stays.What is the value of the postfix expression
7 2 − 3 * 8 4 / +? What is on the stack after each operator?Answer
−: right 2, left 7 → 5; stack 5.*: right 3, left 5 → 15; stack 15./: right 4, left 8 → 2; stack 15 2.+: right 2, left 15 → 17. The value is 17; in infix, (7 − 2) · 3 + 8 / 4.What are the spans for prices
[50, 40, 45, 60, 55, 70]? How many pops doesspanmake in total?Answer
Spans:
[1, 1, 2, 4, 1, 6]. Ati = 2, 45 pops day 1 (40) and stops at day 0 (50): 2 − 0 = 2. Ati = 3, 60 pops days 2 and 0, emptying the stack: 3 + 1 = 4. Ati = 5, 70 pops days 4 and 3, emptying it: 6. That is 5 pops (indices 1, 2, 0, 4, 3), fewer than the 6 pushes.What is the next greater element array for
{3, 8, 2, 5, 1, 9}? Which values are popped at stepi = 1?Answer
The result is
[8, 9, 5, 9, 9, −1]. Right to left: 9 → −1; 1 → 9; 5 pops 1 → 9; 2 → 5; ati = 18 arrives with the stack 9 5 2 bottom to top, pops 2 and then 5, and the 9 left on top is the answer. Finally 3 → 8.An empty stack of capacity 2 that doubles when full receives 12 pushes. How many resizes and copies are made? Is the total cost below 3n?
Answer
Resizes occur on the 3rd
push(2 → 4, 2 copies), the 5th (4 → 8, 4 copies), and the 9th (8 → 16, 8 copies): 3 resizes, 14 copies. With 12 writes the total is 26, below 3 · 12 = 36. The final capacity is 16.Which moves does the stack version of
hanoi(2)print? Which tasks are on the stack (top to bottom) after the first iteration?Answer
In the first iteration
(2,A,B,C)is split, and the stack, top to bottom, becomes(1,A,C,B),(1,A,B,C),(1,B,A,C). The next three iterations carry them out: A → B, A → C, B → C, a total of 22 − 1 = 3 moves.Suppose the min stack’s
pushcondition readsx < min.peek()instead ofx <= min.peek(). What happens onpush(2),push(2),pop(),getMin()from an empty stack?Answer
The first 2 goes onto
min; the second does not, as2 < 2is false.pop()removes a 2, and since2 == min.peek(), the only 2 inmingoes too.sstill holds a 2 butminis empty, somin.peek()ingetMin()returnsnull, and unboxing it tointthrowsNullPointerException.What is the value of the prefix expression
/ * 4 + 1 2 3?Answer
Right to left: 3, 2, 1 are pushed (stack 3 2 1).
+: left 1 (first pop), right 2 → 3; stack 3 3. Push 4; stack 3 3 4.*: left 4, right 3 → 12; stack 3 12./: left 12, right 3 → 4. The value is 4; in infix, 4 · (1 + 2) / 3.
7.10Further reading
- M. T. Goodrich, R. Tamassia, and M. H. Goldwasser, Data Structures and Algorithms in Java (6th ed., 2014), Section 6.1: the stack ADT, array and linked-list implementations, and bracket matching and HTML tag checking, in Java code close to these notes.
- R. Sedgewick and K. Wayne, Algorithms (4th ed., 2011), Section 1.3: stacks implemented with a dynamic array and a linked list, and Dijkstra’s two-stack expression evaluation algorithm in a short, clear example.
- T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein, Introduction to Algorithms (3rd ed., 2009), Sections 10.1 and 17.4: the array implementation of a stack and the amortized analysis of dynamic tables, in more formal language.
7.11References
- T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein, Introduction to Algorithms, 3rd ed., MIT Press, 2009.
- E. W. Dijkstra, “Algol 60 translation: An Algol 60 translator for the X1 and Making a translator for Algol 60”, Report MR 35/61, Mathematisch Centrum, Amsterdam, 1961.
- M. T. Goodrich, R. Tamassia, M. H. Goldwasser, Data Structures and Algorithms in Java, 6th ed., Wiley, 2014.
- R. Lafore, Data Structures and Algorithms in Java, 2nd ed., Sams, 2002.
- Oracle, Java SE 17 API Specification: documentation for
java.util.Dequeandjava.util.ArrayDeque. - R. Sedgewick, K. Wayne, Algorithms, 4th ed., Addison-Wesley, 2011.
Related pages: Chapter 7 slides · Study questions · Glossary · © Sercan Külcü. All rights reserved.