Data Structures: Complete Interview Reference
π Data Structure Comparison
| Structure | Access | Search | Insert | Delete | Space | Best For |
|---|---|---|---|---|---|---|
| Array | O(1) | O(n) | O(n) | O(n) | O(n) | Random access, known size |
| Dynamic Array | O(1) | O(n) | O(1)* | O(n) | O(n) | Appending, random access |
| Linked List | O(n) | O(n) | O(1) | O(1) | O(n) | Frequent insert/delete at head |
| Stack | O(n) | O(n) | O(1) | O(1) | O(n) | LIFO, undo, DFS |
| Queue | O(n) | O(n) | O(1) | O(1) | O(n) | FIFO, BFS, scheduling |
| Hash Map | - | O(1)* | O(1)* | O(1)* | O(n) | Fast lookup, counting |
| Binary Search Tree | O(log n)* | O(log n)* | O(log n)* | O(log n)* | O(n) | Sorted data, range queries |
| Heap | O(n) | O(n) | O(log n) | O(log n) | O(n) | Priority queue, top-K |
| Trie | O(m) | O(m) | O(m) | O(m) | O(n*m) | Prefix search, autocomplete |
| Graph (Adj. List) | - | O(V+E) | O(1) | O(V) | O(V+E) | Sparse graphs, social networks |
| Graph (Adj. Matrix) | O(1) | O(VΒ²) | O(1) | O(1) | O(VΒ²) | Dense graphs, quick edge lookup |
*Amortized / average case
π¦ Arrays
Key Properties
- Contiguous memory β cache-friendly, O(1) random access
- Fixed size (static) or dynamic (resizable with amortized O(1) append)
- Zero-indexed in most languages
Common Operations
# Python (list = dynamic array)
arr = [1, 2, 3, 4, 5]
arr.append(6) # O(1) amortized - add to end
arr.insert(0, 0) # O(n) - add to beginning (shifts elements)
arr.pop() # O(1) - remove from end
arr.pop(0) # O(n) - remove from beginning (shifts)
arr[3] # O(1) - random access
3 in arr # O(n) - linear search
sorted_arr = sorted(arr) # O(n log n)
Interview Patterns with Arrays
- Two Pointers β Pair with target sum, remove duplicates
- Sliding Window β Max subarray, longest substring
- Prefix Sum β Range sum queries
- Sorting + Greedy β Interval problems
Common Mistakes
- Off-by-one errors in loop bounds
- Modifying array while iterating
- Not considering empty array edge case
- Forgetting that
insert(0, x)andpop(0)are O(n)
π Linked Lists
Types
Singly Linked: 1 β 2 β 3 β 4 β None
Doubly Linked: None β 1 β 2 β 3 β 4 β None
Circular: 1 β 2 β 3 β 4 β 1 (back to head)
Key Operations
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
# Insert at head - O(1)
def insert_head(head, val):
new_node = ListNode(val)
new_node.next = head
return new_node
# Delete node (given reference) - O(1)
def delete_node(node):
node.val = node.next.val
node.next = node.next.next
# Reverse linked list - O(n)
def reverse(head):
prev = None
curr = head
while curr:
next_temp = curr.next
curr.next = prev
prev = curr
curr = next_temp
return prev
Interview Patterns with Linked Lists
- Fast & Slow Pointers β Detect cycle, find middle
- Dummy Head β Simplify edge cases in deletion/insertion
- Recursion β Reverse, merge, palindrome check
- Two Pointers β Nth from end, intersection
Classic Problems
- Reverse a linked list (iterative + recursive)
- Detect cycle (Floydβs algorithm)
- Merge two sorted lists
- Remove nth node from end
- Flatten a multilevel linked list
- LRU Cache (doubly linked list + hash map)
πΊοΈ Hash Maps
How They Work
Key β Hash Function β Index β Bucket β Value
Collision Resolution:
βββ Chaining (linked list at each bucket)
βββ Open Addressing (linear/quadratic probing)
Key Properties
- Average O(1) for insert, delete, lookup
- Worst case O(n) with many collisions
- Load factor = n/m (elements/buckets), resize when > 0.75
- Not ordered (use TreeMap/OrderedDict if order matters)
Interview Patterns with Hash Maps
- Frequency Counting β Count occurrences, anagram check
- Two Sum β Store complements as you iterate
- Grouping β Group anagrams, categorize items
- Caching β Memoization, LRU cache
- Set Operations β Union, intersection, difference
# Two Sum - O(n) with hash map
def two_sum(nums, target):
seen = {}
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [seen[complement], i]
seen[num] = i
return []
# Group Anagrams - O(n * k log k)
def group_anagrams(strs):
groups = {}
for s in strs:
key = ''.join(sorted(s))
groups.setdefault(key, []).append(s)
return list(groups.values())
π³ Trees
Binary Tree Traversals
# Inorder (Left, Root, Right) β gives sorted order for BST
def inorder(root):
if root:
inorder(root.left)
print(root.val)
inorder(root.right)
# Preorder (Root, Left, Right) β used for copying/serialization
def preorder(root):
if root:
print(root.val)
preorder(root.left)
preorder(root.right)
# Postorder (Left, Right, Root) β used for deletion, calculating size
def postorder(root):
if root:
postorder(root.left)
postorder(root.right)
print(root.val)
# Level Order (BFS) β level by level
from collections import deque
def level_order(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level = []
for _ in range(len(queue)):
node = queue.popleft()
level.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
result.append(level)
return result
Binary Search Tree (BST)
Properties:
- Left subtree values < root < Right subtree values
- Inorder traversal gives sorted sequence
- Average O(log n) for search/insert/delete
- Worst case O(n) if unbalanced (degenerates to linked list)
Balanced BSTs:
- AVL Tree: strict balance (height diff β€ 1)
- Red-Black Tree: relaxed balance, fewer rotations
- Used in: TreeMap (Java), std::map (C++)
Tree Interview Patterns
- Recursion β Most tree problems are naturally recursive
- BFS (Level Order) β Level-by-level processing
- DFS (Pre/In/Post) β Path finding, validation
- Divide & Conquer β Split into left/right subtrees
Classic Tree Problems
- Maximum depth / height
- Validate BST
- Lowest common ancestor
- Serialize/deserialize binary tree
- Diameter of binary tree
- Path sum (all paths, any path)
- Binary tree to doubly linked list
- Construct tree from inorder + preorder
π Heaps (Priority Queues)
Properties
Min Heap: Parent β€ Children (smallest at top)
Max Heap: Parent β₯ Children (largest at top)
Operations:
βββ insert: O(log n)
βββ extract_min/max: O(log n)
βββ peek: O(1)
βββ build_heap: O(n)
Python Implementation
import heapq
# Min heap (default in Python)
heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 2)
min_val = heapq.heappop(heap) # 1
# Max heap (negate values)
max_heap = []
heapq.heappush(max_heap, -3)
heapq.heappush(max_heap, -1)
max_val = -heapq.heappop(max_heap) # 3
# Build heap from list - O(n)
nums = [3, 1, 4, 1, 5, 9]
heapq.heapify(nums)
# Top K elements
top_k = heapq.nlargest(k, nums)
Interview Patterns with Heaps
- Top-K Elements β K largest/smallest, K most frequent
- Merge K Sorted β Use min heap to track smallest across K lists
- Median Finding β Two heaps (max heap for lower half, min heap for upper)
- Task Scheduler β Priority-based scheduling
- Sliding Window Maximum β Monotonic deque or heap
π Graphs
Representations
# Adjacency List (most common in interviews)
graph = {
'A': ['B', 'C'],
'B': ['A', 'D'],
'C': ['A', 'D'],
'D': ['B', 'C']
}
# Adjacency Matrix
# A B C D
# A [[0, 1, 1, 0],
# B [1, 0, 0, 1],
# C [1, 0, 0, 1],
# D [0, 1, 1, 0]]
# Edge List
edges = [('A', 'B'), ('A', 'C'), ('B', 'D'), ('C', 'D')]
BFS vs DFS
BFS (Breadth-First Search):
βββ Uses Queue (FIFO)
βββ Explores level by level
βββ Shortest path in unweighted graph
βββ Space: O(V) for queue
βββ Use: Shortest path, level order, connected components
DFS (Depth-First Search):
βββ Uses Stack (or recursion)
βββ Explores as deep as possible
βββ Better for path finding, cycle detection
βββ Space: O(V) for stack/recursion
βββ Use: Topological sort, cycle detection, maze solving
Graph Interview Patterns
- BFS β Shortest path, word ladder, rotting oranges
- DFS β Number of islands, course schedule, clone graph
- Topological Sort β Task scheduling, dependency resolution
- Union Find β Connected components, redundant connection
- Dijkstra β Weighted shortest path
- Backtracking β All paths, permutations
Classic Graph Problems
- Number of islands (DFS/BFS)
- Course schedule (topological sort)
- Clone graph (DFS + hash map)
- Word ladder (BFS)
- Network delay time (Dijkstra)
- Accounts merge (Union Find)
- Alien dictionary (topological sort)
π€ Tries
Structure
Trie for ["cat", "car", "card", "dog", "dot"]:
root
/ \
c d
| |
a o
/ \ / \
t r g t
|
d
Implementation
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end = True
def search(self, word):
node = self.root
for char in word:
if char not in node.children:
return False
node = node.children[char]
return node.is_end
def starts_with(self, prefix):
node = self.root
for char in prefix:
if char not in node.children:
return False
node = node.children[char]
return True
When to Use Tries
- Autocomplete / word suggestions
- Spell checking
- IP routing (longest prefix match)
- Word search in 2D grid
π Cross-References
- Problem Patterns β See which data structures pair with which patterns
- Complexity Analysis β Big-O for all operations
- Coding Framework β How to select the right data structure
- Cheatsheets β Quick reference for CS fundamentals