FAANG Interview Prep
Foundations, Memory & Complexity
Big-O notation, time/space analysis, memory layoutRecursion Complete Guide
Base cases, call stack, tail recursion, memoizationArrays & Array ADT
Static/dynamic arrays, operations, amortized analysisStrings
Pattern matching, string algorithms, encoding, manipulationMatrices
2D arrays, sparse matrices, matrix operations, traversalsLinked Lists
Singly, doubly, circular lists, pointer manipulationStack
LIFO, push/pop, expression evaluation, backtrackingQueue
FIFO, circular queue, deque, priority queueTrees
Binary trees, traversals, expression trees, threaded treesBST & Balanced Trees
Search, insert, delete, AVL, red-black, B-treesHeaps, Sorting & Hashing
Min/max heaps, heapsort, hash tables, collision handlingGraphs, DP, Greedy & Backtracking
BFS, DFS, shortest paths, dynamic programming, optimizationIntroduction to Stack
A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. Think of it like a stack of plates - you can only add or remove plates from the top. Stacks are fundamental in computer science, used in function calls, expression parsing, undo mechanisms, and many algorithms.
Stack Operations
Core Stack Operations
| Operation | Description | Time |
|---|---|---|
| push(item) | Add item to top | \(O(1)\) |
| pop() | Remove and return top item | \(O(1)\) |
| peek()/top() | Return top item without removing | \(O(1)\) |
| isEmpty() | Check if stack is empty | \(O(1)\) |
| size() | Return number of items | \(O(1)\) |
Stack Implementations
Array-Based Stack
The simplest stack implementation uses an array (or dynamic list in Python). A top variable tracks the index of the topmost element. Push increments top and places the element; pop returns the element at top and decrements. All operations are \(O(1)\) amortized. The array approach offers excellent cache locality (contiguous memory) but has a fixed capacity in static arrays. Python lists and Java's ArrayList handle resizing automatically via amortized doubling.
Fixed-Size Array Stack (Lower Level)
Linked List-Based Stack
A linked-list stack pushes and pops at the head — making every operation truly \(O(1)\) with no amortized cost and no capacity limit. Each push allocates a new node; each pop frees the head. The tradeoff: each element requires extra memory for the pointer (8 bytes on 64-bit systems), and nodes are scattered in memory (poor cache performance). Use this when you need guaranteed \(O(1)\) worst-case (no resize pauses) or when implementing stacks in languages without dynamic arrays.
Array vs Linked List Stack
| Aspect | Array Stack | Linked List Stack |
|---|---|---|
| Push | \(O(1)\) amortized* | \(O(1)\) |
| Pop | \(O(1)\) | \(O(1)\) |
| Memory | Contiguous, less overhead | Scattered, pointer overhead |
| Cache | Better locality | Poor locality |
| Size | Fixed or resize needed | Dynamic |
* \(O(n)\) worst case when resizing, but amortized \(O(1)\)
Stack Applications
Parentheses Matching
The balanced parentheses problem is the canonical stack application. The insight: every closing bracket must match the most-recently-opened bracket — exactly LIFO order. Push opening brackets; on a closing bracket, pop and verify it matches. If the stack is empty when you need to pop (extra closer) or non-empty after processing (unclosed opener), the expression is unbalanced. This pattern extends to HTML tag matching, code block nesting, and compiler syntax validation.
Expression Evaluation
Python - Prefix Evaluation
# Prefix Expression Evaluation
# Process right to left
def evaluate_prefix(expression):
stack = []
operators = {'+', '-', '*', '/', '^'}
tokens = expression.split()[::-1] # Reverse
for token in tokens:
if token not in operators:
stack.append(float(token))
else:
a = stack.pop() # First operand
b = stack.pop() # Second operand
if token == '+': stack.append(a + b)
elif token == '-': stack.append(a - b)
elif token == '*': stack.append(a * b)
elif token == '/': stack.append(a / b)
elif token == '^': stack.append(a ** b)
return stack[0]
# Test
print(evaluate_prefix("+ 3 4")) # 7
print(evaluate_prefix("* + 3 4 5")) # 35
Infix to Postfix Conversion
Monotonic Stack
A monotonic stack maintains elements in either increasing or decreasing order. It's a powerful technique for solving problems involving "next greater/smaller element" patterns.
Next Greater Element
For each element in an array, find the first element to its right that is larger. The brute force is \(O(n^2)\) (nested loops). The monotonic stack solves it in \(O(n)\): maintain a decreasing stack (stack top is always the smallest unresolved element). When a new element is larger than the stack top, it IS the "next greater" for that top element — pop and record the answer. Each element is pushed and popped at most once, guaranteeing \(O(n)\) total.
Python - Circular Array
# Next Greater Element (Circular Array)
def next_greater_circular(nums):
n = len(nums)
result = [-1] * n
stack = []
# Process array twice to handle circular nature
for i in range(2 * n):
idx = i % n
while stack and nums[stack[-1]] < nums[idx]:
result[stack.pop()] = nums[idx]
if i < n:
stack.append(idx)
return result
# Test
print(next_greater_circular([1, 2, 1])) # [2, -1, 2]
Python - Next Smaller Element
# Next Smaller Element - Monotonic Increasing Stack
def next_smaller_element(nums):
n = len(nums)
result = [-1] * n
stack = []
for i in range(n):
while stack and nums[stack[-1]] > nums[i]:
result[stack.pop()] = nums[i]
stack.append(i)
return result
# Test
nums = [4, 5, 2, 10, 8]
print(next_smaller_element(nums)) # [2, 2, -1, 8, -1]
Daily Temperatures
LeetCode Practice Problems
Easy 20. Valid Parentheses
Determine if the input string has valid bracket pairs.
Easy 155. Min Stack
Design a stack that supports getMin() in \(O(1)\).
Medium 150. Evaluate Reverse Polish Notation
Evaluate postfix expression given as array of tokens.
Medium 739. Daily Temperatures
Find how many days to wait for warmer temperature.
Medium 84. Largest Rectangle in Histogram
Find the largest rectangle in a histogram.
Hard 32. Longest Valid Parentheses
Find the length of the longest valid parentheses substring.
Next in the Series
In Part 8: Queue, we’ll complement the stack with queues — FIFO, circular, deque, and priority queue implementations that power BFS, scheduling, and sliding-window problems.