Stack Fundamentals
Stack Fundamentals
A stack is a Last In First Out (LIFO) data structure. Think of a stack of plates - you can only add or remove from the top.
Core Operations
| Operation | Description | Time |
|---|---|---|
| push | Add element to top | O(1) |
| pop | Remove element from top | O(1) |
| peek/top | View top element | O(1) |
| isEmpty | Check if empty | O(1) |
Visual Example
push(1) → [1]
push(2) → [1, 2]
push(3) → [1, 2, 3]
peek() → returns 3
pop() → returns 3, stack becomes [1, 2]
Java Stack Implementation
// Using Java's built-in Stack
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
int top = stack.peek(); // 2
int val = stack.pop(); // 2
boolean empty = stack.isEmpty(); // false
// Using Deque (preferred in modern Java)
Deque<Integer> deque = new ArrayDeque<>();
deque.push(1); // or deque.addFirst(1)
deque.push(2);
int top = deque.peek(); // 2
int val = deque.pop(); // 2
Why Use Stack?
- Undo operations - text editors, browsers
- Function calls - recursion uses call stack
- Expression evaluation - postfix, infix
- Balanced parentheses - compiler syntax checking
- Depth-First Search - graph traversal
- Backtracking - maze solving, permutations
Stack Applications
Stack Applications
1. Balanced Parentheses (LeetCode 20)
public boolean isValid(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 top = stack.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
}
}
return stack.isEmpty();
}
// Time: O(n), Space: O(n)
2. Min Stack (LeetCode 155)
class MinStack {
Deque<Integer> stack;
Deque<Integer> minStack;
public MinStack() {
stack = new ArrayDeque<>();
minStack = new ArrayDeque<>();
}
public void push(int val) {
stack.push(val);
if (minStack.isEmpty() || val <= minStack.peek()) {
minStack.push(val);
}
}
public int pop() {
int val = stack.pop();
if (val == minStack.peek()) {
minStack.pop();
}
return val;
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
3. Evaluate Reverse Polish Notation (LeetCode 150)
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new ArrayDeque<>();
for (String token : tokens) {
switch (token) {
case "+": stack.push(stack.pop() + stack.pop()); break;
case "-":
int b = stack.pop(), a = stack.pop();
stack.push(a - b);
break;
case "*": stack.push(stack.pop() * stack.pop()); break;
case "/":
b = stack.pop(); a = stack.pop();
stack.push(a / b);
break;
default: stack.push(Integer.parseInt(token));
}
}
return stack.pop();
}
4. Next Greater Element (LeetCode 496)
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Map<Integer, Integer> map = new HashMap<>();
Deque<Integer> stack = new ArrayDeque<>();
for (int num : nums2) {
while (!stack.isEmpty() && stack.peek() < num) {
map.put(stack.pop(), num);
}
stack.push(num);
}
int[] result = new int[nums1.length];
for (int i = 0; i < nums1.length; i++) {
result[i] = map.getOrDefault(nums1[i], -1);
}
return result;
}
When to Use Stack
| Problem Type | Pattern |
|---|---|
| Balanced parentheses | Push open, pop on close |
| Next greater/smaller | Monotonic stack |
| Expression evaluation | Shunting yard |
| Undo operations | Push state, pop to undo |
| DFS traversal | Push neighbors |
Interactive Visualization
Stack Push/Pop Operations
Practice Problems
Given a string `s` containing just the characters `'('`, `')'`, `'{'`, `'}'`, `'['` and `']'`, determine if the input string is valid. An input string is valid if: 1. Open brackets must be closed by the same type of brackets. 2. Open brackets must be closed in the correct order. 3. Every close bracket has a corresponding open bracket of the same type.
Example:
Input: s = "()"
Output: true
Simple parentheses match.
Edge Cases:
- Single opening bracket: "("
- Single closing bracket: ")"
- Odd-length string (always invalid)
- Deeply nested: "(((())))"
- Interleaved mismatched: "([)]"
Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.
Example:
Input: MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.getMin(); minStack.pop(); minStack.top(); minStack.getMin();
Output: [null, null, null, -3, null, 0, -2]
Min stack tracks minimum at each level.
Edge Cases:
- Pushing duplicate minimums: both should be tracked
- Single element: getMin returns that element
- All same values: min stack grows with main stack
Given an array of temperatures, return an array answer where answer[i] is the number of days you have to wait to get a warmer temperature.
Example:
Input: temperatures = [73,74,75,71,69,72,76,73]
Output: [1,1,4,2,1,1,0,0]
Wait 1 day for 74, 4 days for 76, etc.
Edge Cases:
- All same temperature — all answers are 0
- Strictly decreasing — all answers are 0 except last
- Strictly increasing — answers are [1,1,...,1,0]
- Single element — answer is [0]
- Two elements, second warmer — answer is [1,0]
Given an array of integers heights representing the histogram's bar height, find the area of the largest rectangle in the histogram.
Example:
Input: heights = [2,1,5,6,2,3]
Output: 10
The largest rectangle is formed by bars at index 2 and 3 with height 5 and width 2.
Edge Cases:
- All same heights: area = n * height
- Strictly increasing: area = max element * 1
- Strictly decreasing: area = n * heights[0]
Given an array of integers temperatures, return an array answer such that answer[i] is the number of days you have to wait after the ith day to get a warmer temperature.
Example:
Input: temperatures = [73,74,75,71,69,72,76,73]
Output: [1,1,4,2,1,1,0,0]
Day 0 (73): wait 1 day for 74. Day 2 (75): wait 4 days for 76.
Edge Cases:
- All same temperatures: all zeros
- Strictly increasing: [1,2,3,4] → [1,1,1,0]
- Strictly decreasing: [4,3,2,1] → [0,0,0,0]
Given an array of integers heights representing the histogram's bar height, find the area of the largest rectangle in the histogram.
Example:
Input: heights = [2,1,5,6,2,3]
Output: 10
The largest rectangle is formed by bars at index 2 and 3 with height 5 and width 2.
Optimal Solution — O(n) time, O(n) space
Monotonic stack: find left and right boundaries for each bar
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
Deque<Integer> stack = new ArrayDeque<>();
int maxArea = 0;
for (int i = 0; i <= n; i++) {
int h = (i == n) ? 0 : heights[i];
while (!stack.isEmpty() && h < heights[stack.peek()]) {
int height = heights[stack.pop()];
int width = stack.isEmpty() ? i : i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
return maxArea;
}
} Edge Cases:
- Single bar
- Already sorted
- All same heights
Quiz
1. What principle does a stack follow?
2. What is the time complexity of push and pop operations?
3. What is a common mistake when implementing Stack?
Flashcards
Question
What is LIFO?
Click to reveal answer
Answer
Last In First Out - the most recently added element is removed first.
Question
When should I use a stack?
Click to reveal answer
Answer
For balanced parentheses, expression evaluation, undo operations, DFS, and backtracking.
Question
Stack best practices
Click to reveal answer
Answer
Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.
Revision Notes
Key Takeaways
- 1. Stack is LIFO
- 2. All operations are O(1)
- 3. Use for balanced parentheses and expression evaluation
- 4. Deque is preferred over Stack class
Interview Tips
- • Always mention LIFO principle
- • Discuss when to use stack vs queue
- • Explain space complexity for nested structures
Cheat Sheet
Stack Cheat Sheet
Operations: push, pop, peek, isEmpty - all O(1)
Use Cases: Balanced parentheses, expression eval, undo, DFS
Java: Use Deque interface with ArrayDeque
Pattern: Push on open bracket, pop on close bracket