Revise on one page
Every chapter's costs, top mistakes and terms. Print one chapter, or print this page for all eight.
Introduction to Data Structures
dsa.fetehadin.comA data structure is a way of organising data in memory so the operations you need are fast. Learn how they are classified and what makes a good one.
| Array: read item i | O(1) |
| Array: insert at the front | O(n): shift everything |
| Linked list: insert at the front | O(1) |
| Linked list: read item i | O(n): walk from the head |
- ✗Calling a stack "non-linear". Stacks and queues are linear; only trees and graphs are non-linear here.
- ✗Saying an array is dynamic. A C++ array has a fixed size;
vectorand linked lists are dynamic. - ✗Confusing a data structure (how data is stored) with an algorithm (the steps that use it).
An Abstract Data Type says WHAT operations exist; a data structure says HOW they are built. Separating the two is the key idea of this course.
| push / pop / top (array implementation) | O(1) |
| push / pop / top (linked implementation) | O(1) |
| Maximum size | array: fixed · linked list: limited only by memory |
- ✗Saying an ADT "is" an array. An ADT has no storage details at all; an array can only implement one.
- ✗Listing implementation details (like
topindex) as part of the ADT's definition. - ✗Forgetting error conditions. "pop on an empty stack is an error" IS part of the ADT.
Arrays store items side by side in memory, which is why reading any index is instant. 2-D arrays are stored row by row.
| Access a[i] | O(1) |
| Search (unsorted) | O(n) |
| Insert / delete at position i | O(n): shift items |
| Insert / delete at the end | O(1) if there is space |
| Traverse an R × C matrix | O(R·C) |
| Space | O(n) for n items |
- ✗Using 1-based indexes. C++ arrays start at 0, so the last valid index is n − 1.
- ✗Writing
a[n](out of bounds). C++ does not check, so it silently corrupts memory. - ✗Using
sizeof(a)/sizeof(a[0])inside a function. There,ais a pointer, so the trick gives the wrong size. Passninstead.
- Data structure:
- A way of organising data in memory so that the operations you need are efficient.
- Primitive data structure:
- Built-in types operated on directly by the machine: int, char, float, bool, pointers.
- Linear data structure:
- Items form a sequence, each with at most one predecessor and one successor: arrays, linked lists, stacks, queues.
- Non-linear data structure:
- Items branch or connect freely: trees and graphs.
- Static vs dynamic:
- Static structures have a size fixed when they are created (arrays); dynamic ones grow and shrink at run time (linked lists).
- ADT (Abstract Data Type):
- A description of data plus the operations on it, saying what they do but not how they are implemented.
- Abstraction:
- Hiding implementation details behind an interface so users only see what they need.
- Row-major order:
- How C++ stores a 2-D array: row 0 first, then row 1. Address of a[i][j] = base + (i·cols + j)·size.
Algorithm & Algorithm Analysis
dsa.fetehadin.comAn algorithm is a finite, unambiguous sequence of steps that solves a problem. Learn the 11 properties every correct algorithm must have.
| findMax: comparisons | exactly n − 1 |
| findMax: time | O(n), and best = worst |
| Space | O(1) |
- ✗Confusing definiteness (each step has one clear meaning) with finiteness (the algorithm stops).
- ✗Saying an algorithm needs at least one input. It needs zero or more inputs but at least one output.
- ✗Treating "a program" and "an algorithm" as the same. A program may run forever (an OS); an algorithm must terminate.
Empirical vs theoretical analysis, qualitative vs quantitative, the three classic avoidable inefficiencies, and the time–space trade-off.
| Search with late termination | always n checks |
| Search that breaks early | 1 … n checks |
| Duplicate check with nested loops | O(n²) time, O(1) space |
| Duplicate check with a set | O(n) time, O(n) space ← trade-off |
- ✗Saying empirical analysis is useless. It is useful, but its results depend on the machine, compiler, language and inputs.
- ✗Mixing up "redundant computation" (recomputing the same value in a loop) with "repeated array referencing" (reading
a[i]many times). - ✗Thinking the faster algorithm is always better. It may need far more memory.
Turn code into a formula T(n) using the instructor's counting rules. Loops become sums, and nested loops multiply.
Single loop for(i=0;i<n;i++) k=k+1; cout<<k; | T(n) = 4n + 3 → O(n) |
Nested n × n loop with k++ | T(n) = 3n² + 4n + 3 → O(n²) |
Loop where i doubles (i *= 2) | ≈ log₂ n iterations → O(log n) |
- ✗Counting the loop test n times instead of n + 1 (the last, failing test counts too).
- ✗Counting
k = k + 1as 1 unit. It is 2: one addition and one assignment. - ✗For
if/else, adding both branches. Count the condition plus the larger branch only.
The same algorithm can be fast or slow depending on the input. We analyse the best, average and worst case, and usually report the worst.
| Linear search: best (key is first) | O(1): 1 comparison |
| Linear search: average (key present) | (n + 1) / 2 comparisons → O(n) |
| Linear search: worst (last or missing) | n comparisons → O(n) |
| Bubble sort with flag: best / worst | O(n) / O(n²) |
| Space | O(1) |
- ✗"Best case is when n is small." No: best case is about the arrangement of the input for a given n, never about a small n.
- ✗Thinking the average case is (best + worst) / 2. It is an expected value over all inputs.
- ✗Mixing up the case with the notation. "Worst case" is not the same thing as "Big-O".
O, Ω, Θ, o and ω describe how fast T(n) grows for large n. Learn the definitions, the order of growth, and how to prove f(n) = O(g(n)) by finding c and k.
| O(1) | constant: array access, push/pop |
| O(log n) | logarithmic: binary search |
| O(n) | linear: linear search, traversal |
| O(n log n) | linearithmic: merge sort, heap sort |
| O(n²) | quadratic: bubble, selection, insertion sort |
| O(n³) | cubic: three nested loops |
| O(2ⁿ) | exponential: all subsets, naive Fibonacci |
- ✗Forgetting to state c and k explicitly. A proof without numbers gets partial marks.
- ✗Claiming 3n² = O(n). False: no constant c makes 3n² ≤ c·n for all large n.
- ✗Thinking Big-O means "worst case". Big-O is an upper bound on a function; you can give an upper bound for the best case too.
- Algorithm:
- A finite, well-defined sequence of steps that turns input into the desired output.
- Finiteness:
- An algorithm must stop after a finite number of steps.
- Definiteness:
- Every step is precise and unambiguous.
- Empirical analysis:
- Measuring an algorithm by running it and timing it. Depends on the machine and the input.
- Theoretical analysis:
- Counting basic operations as a function of input size n. Machine-independent.
- Time unit:
- One basic operation (assignment, single I/O, Boolean or arithmetic op, return) counted as 1 in T(n).
- T(n):
- The running time of an algorithm as a function of input size n, in time units.
- Best / average / worst case:
- The running time on the most favourable, typical, and least favourable input of size n.
- Big-O (O):
- Upper bound: f(n) = O(g(n)) if f(n) ≤ c·g(n) for all n ≥ k, for some constants c > 0 and k.
- Big-Omega (Ω):
- Lower bound: f(n) ≥ c·g(n) for all n ≥ k.
- Big-Theta (Θ):
- Tight bound: f(n) is both O(g(n)) and Ω(g(n)).
- Little-o / little-omega:
- Strict bounds: f grows strictly slower (o) or strictly faster (ω) than g.
- Time/space trade-off:
- Using more memory to save time (or the reverse), e.g. storing results instead of recomputing them.
Simple Sorting & Searching
dsa.fetehadin.comFind the smallest remaining item and swap it to the front. Repeat. Simple, at most n − 1 swaps, but always O(n²) comparisons.
| Best case | O(n²): it still scans for the minimum every pass |
| Average case | O(n²) |
| Worst case | O(n²): n(n−1)/2 comparisons |
| Swaps | at most n − 1 (the fewest of the simple sorts) |
| Space | O(1) |
| Stable · in-place | no · yes |
- ✗Swapping inside the inner loop every time a smaller item is found. Swap once, after the inner loop.
- ✗Starting the inner loop at
j = iinstead ofj = i + 1(compares an item with itself). - ✗Claiming selection sort has an O(n) best case. It has none; it always scans.
Compare neighbours and swap them if they are out of order. After each pass the biggest remaining item has "bubbled" to the end.
| Best case (already sorted) | O(n): one pass, no swaps, stop |
| Average case | O(n²) |
| Worst case (reversed) | O(n²): n(n−1)/2 comparisons and swaps |
| Space | O(1) |
| Stable · in-place | yes · yes |
- ✗Looping
jup ton - 1in every pass instead ofn - 1 - i. It still works, but does useless comparisons. - ✗Writing
a[j] >= a[j+1]. Swapping equal items breaks stability. - ✗Reading past the array with
j < nand then accessinga[j + 1].
Grow a sorted part on the left by inserting each new item into its correct place, like sorting cards in your hand. Fast on nearly sorted data.
| Best case (already sorted) | O(n): n − 1 comparisons, 0 shifts |
| Average case | O(n²) |
| Worst case (reversed) | O(n²): n(n−1)/2 comparisons and shifts |
| Space | O(1) |
| Stable · in-place | yes · yes |
- ✗Writing
a[j] >= keyin the while loop. That breaks stability. - ✗Forgetting
j >= 0in the loop condition, which readsa[-1]. - ✗Putting
a[j + 1] = keyinside the while loop. It goes after the loop.
When records are big, don't move them. Sort an array of pointers (or indices) to the records instead, and only small addresses get swapped.
| Comparisons | same as the underlying sort (here selection: n(n−1)/2) |
| Data moved per swap | one pointer (8 bytes) instead of one whole record |
| Time | O(n²) with selection sort; O(n log n) with a fast sort |
| Space | O(n) for the pointer array |
| Stable · in-place | no · no |
- ✗Swapping the records (
swap(*p[i], *p[min])) instead of the pointers (swap(p[i], p[min])). That defeats the purpose. - ✗Comparing the pointers themselves (
p[j] < p[min]) instead of what they point to (p[j]->score). - ✗Forgetting to initialise
p[i] = &s[i]before sorting.
Check every item from left to right until you find the key or run out. Works on any array, sorted or not, but costs O(n).
| Best case | O(1): key at index 0 |
| Average case (found) | (n + 1)/2 comparisons → O(n) |
| Worst case | O(n): last item or not present |
| Needs sorted data? | No |
| Space | O(1) |
- ✗Returning
-1inside the loop (in anelse), which stops after checking only the first item. - ✗Looping with
i <= n, one past the end. - ✗Forgetting that an unsorted array cannot use binary search.
On a sorted array, check the middle, then throw away the half that cannot contain the key. Each step halves the problem, so it takes O(log n).
| Best case | O(1): key is at the first mid |
| Average / worst case | O(log n): about ⌊log₂ n⌋ + 1 comparisons |
| 1,000,000 items | at most 20 comparisons |
| Needs sorted data? | Yes, and random access (an array, not a linked list) |
| Space | O(1) iterative · O(log n) recursive (call stack) |
- ✗Using
while (low < high)instead oflow <= high. That misses a key when one item is left. - ✗Setting
low = midorhigh = midinstead ofmid ± 1, which can loop forever. - ✗Running it on unsorted data. The result is meaningless.
A function that calls itself on a smaller problem until it reaches a base case. You need this for trees (Ch 6) and merge/quick sort (Ch 8).
| factorial(n): time | O(n): n calls, O(1) work each |
| factorial(n): space | O(n) stack frames |
| recursive binary search: time | O(log n) |
| recursive binary search: space | O(log n) stack frames (iterative version: O(1)) |
- ✗No base case, or a base case that is never reached (e.g.
factorial(n + 1)). - ✗Forgetting to
returnthe recursive call's result (bs(...)instead ofreturn bs(...)). - ✗Thinking recursion is always slower or always better. It trades stack memory for simpler code.
- Stable sort:
- A sort that keeps equal keys in their original relative order.
- In-place:
- Uses only O(1) extra memory besides the input (recursion stack aside).
- Pass:
- One full sweep of the outer loop of a sort.
- Pointer sort:
- Sorting an array of pointers (or indices) to records instead of moving the records themselves.
- Linear search:
- Check each item in turn. O(n). Works on unsorted data.
- Binary search:
- Repeatedly halve a sorted range by comparing with the middle item. O(log n).
- Recursion:
- A function that calls itself on a smaller input, stopping at a base case.
- Base case:
- The input a recursive function answers directly, without recursing.
- Call stack:
- Memory where each active function call keeps its parameters and local variables.
Linked Lists
dsa.fetehadin.comA pointer holds an address. new asks the heap for memory that lives until you delete it. Master this and linked lists become easy.
&x, *p, p->field | O(1) |
new / delete one object | O(1) (amortised; done by the allocator) |
| Stack memory | automatic: freed when the function returns |
| Heap memory | manual: lives until delete |
- ✗Dereferencing
nullptror an uninitialised pointer, which crashes (segmentation fault). - ✗Using memory after
delete(dangling pointer). Set the pointer tonullptrright after. - ✗
new[]paired withdeleteinstead ofdelete[].
Nodes on the heap, each pointing to the next. Insert at front, end or a position, delete by value, search and traverse, plus the dummy-node trick and the classic pitfalls.
| Insert at front | O(1) |
| Insert at end | O(n), or O(1) with a tail pointer |
| Insert at position k | O(k) |
| Delete by value / search | O(n) |
| Access the i-th item | O(n): no random access |
| Space | O(n) plus one pointer per node |
- ✗Losing the list by updating
headbefore linking the new node (head = n;first). - ✗Dereferencing
temp->nextwithout checkingtemp != nullptr. - ✗Forgetting the empty-list and delete-the-head special cases.
Each node points both forward and backward. You can walk in both directions and delete a node knowing only its address, at the cost of one extra pointer per node.
| Insert / delete at front or end (with tail) | O(1) |
| Delete a node you already have a pointer to | O(1): no need to find prev |
| Search / delete by value | O(n) |
| Traverse backward | O(n): impossible in a singly list without extra memory |
| Space | O(n), with 2 pointers per node |
- ✗Updating
nextbut forgettingprev(or the reverse). Each insert or delete touches both directions. - ✗Forgetting
cur->next->prevwhencur->nextisnullptr(deleting the tail) → crash. - ✗Not updating
tailwhen deleting the last node.
- Pointer:
- A variable that stores a memory address.
- Heap (memory):
- Memory for dynamic allocation with new/delete. Lives until you free it.
- Memory leak:
- Heap memory that is no longer reachable but was never deleted.
- Dangling pointer:
- A pointer to memory that has already been freed.
- Node:
- One element of a linked structure: data plus pointer(s) to other nodes.
- Head:
- Pointer to the first node of a linked list.
- Dummy (sentinel) node:
- A fake node placed before the head so every real node has a predecessor.
- Doubly linked list:
- Each node has both next and prev pointers, allowing backward traversal and O(1) delete given a node.
Stacks & Queues
dsa.fetehadin.comLast in, first out. The stack ADT (push, pop, peek, isEmpty, isFull) built on a fixed array with a top index, plus overflow and underflow.
| push | O(1) |
| pop | O(1) |
| peek / isEmpty / isFull | O(1) |
| search for a value | O(n): not a stack operation |
| Space | O(MAX), fixed when the array is declared |
- ✗Writing
st[top++] = xwithtop = -1(writes to index −1). Usest[++top] = x. - ✗Popping without checking
isEmpty()(underflow). - ✗Pushing without checking
isFull()(writes past the array).
The same stack ADT, but the top is the head of a linked list. No fixed capacity, push and pop stay O(1), and the bracket-matching application.
| push (insert at head) | O(1) |
| pop (delete head) | O(1) |
| peek / isEmpty | O(1) |
| Space | O(n), one node (data + pointer) per item |
- ✗Using the tail as the top, making pop O(n).
- ✗Forgetting
deletein pop (memory leak). - ✗Reading
top->dataafter deleting the node. Save the value first.
First in, first out. Why a naive array queue makes dequeue O(n), how a front index makes it O(1), and the wasted-slot problem that leads to the circular queue.
| enqueue (at rear) | O(1) |
| dequeue, naive (shift left) | O(n) |
| dequeue, with front index | O(1) |
| front / isEmpty | O(1) |
| Space | O(MAX) |
- ✗Confusing front and rear (items enter at the rear, leave at the front).
- ✗Dequeuing without checking for empty.
- ✗With a front index, testing
rear == -1for empty. The correct test isfront > rear.
Keep two pointers, front and rear. Enqueue at the rear and dequeue at the front, both O(1), with no capacity limit.
| enqueue (insert after rear) | O(1) |
| dequeue (delete front) | O(1) |
| peek / isEmpty | O(1) |
| Space | O(n) |
- ✗Dequeuing from the tail (needs the previous node, so O(n)).
- ✗Forgetting
rear = nullptrwhen the queue becomes empty. The next enqueue then writes through a dangling pointer. - ✗Enqueue into an empty queue without setting both front and rear.
Let front and rear wrap around with the modulo operator, so freed slots are reused. All operations O(1) on a fixed array.
| enqueue | O(1) |
| dequeue | O(1) |
| front / isEmpty / isFull | O(1) |
| Space | O(N), and every slot is usable |
- ✗Writing
rear++instead ofrear = (rear + 1) % N. - ✗Testing
front == rearfor both empty and full. Without acount(or one spare slot) they look the same. - ✗Printing from
0torearinstead of starting atfrontand wrapping.
The standard library versions you will use in real code and on LeetCode, their method names, and the classic traps (pop returns void, empty checks).
| stack push / pop / top | O(1) |
| queue push / pop / front / back | O(1) |
| deque push/pop at either end | O(1) |
| deque d[i] | O(1) |
| Space | O(n) |
- ✗Writing
int x = s.pop();. It does not compile;pop()returnsvoid. Readtop()first. - ✗Calling
top()/front()/pop()on an empty container (undefined behaviour). Checkempty(). - ✗Using
q.top()on a queue (it isfront()), ors.front()on a stack (it istop()).
- Stack:
- LIFO collection: push and pop at the top only.
- LIFO:
- Last In, First Out.
- Overflow / underflow:
- Pushing onto a full stack / popping from an empty one.
- Queue:
- FIFO collection: enqueue at the rear, dequeue from the front.
- FIFO:
- First In, First Out.
- Circular queue:
- Array queue whose indices wrap around with % N so freed slots are reused.
- Deque:
- Double-ended queue: insert and remove at both ends in O(1).
Trees
dsa.fetehadin.comRoot, parent, child, leaf, sibling, depth, height, level, subtree; full, complete and perfect binary trees; and the Node struct with recursive count and height.
| count nodes / leaves | O(n): visits every node once |
| height | O(n) |
| find a value (no ordering) | O(n) |
| Space | O(h) recursion stack, where h is the height |
- ✗Mixing up depth (from the root down to a node) and height (from a node down to its deepest leaf).
- ✗Not stating your height convention. Here, a single node has height 0 and an empty tree −1. Some books count nodes instead of edges.
- ✗Thinking "complete" and "full" mean the same thing.
Left is smaller, right is bigger. Insert, search, find min and max by walking one path, and why the tree's shape decides whether that is O(log n) or O(n).
| search / insert, balanced | O(log n) |
| search / insert, skewed | O(n) |
| min / max | O(h) |
| inorder (sorted output) | O(n) |
| Space | O(n) for the nodes, O(h) recursion |
- ✗Writing
insert(root->left, x);without assigning the result back toroot->left. - ✗Checking only a node's children, not the whole subtree, when asked "is this a BST?".
- ✗Forgetting the
nullptrcheck before readingroot->data.
Deleting a leaf, a node with one child, and a node with two children by copying the inorder successor. The most-asked BST exam question.
| delete, balanced | O(log n) |
| delete, skewed | O(n) |
| Space | O(h) recursion |
- ✗Case 3: copying the successor value but forgetting to delete the successor node itself (you get a duplicate).
- ✗Case 3: searching for the successor in the left subtree. Successor = min of the right subtree.
- ✗Not returning the new subtree root, so the parent still points at the deleted node.
Preorder, inorder and postorder (depth-first, recursive) and level order (breadth-first, with a queue). Know them by hand and in code.
| pre / in / post order | O(n): every node once |
| level order | O(n) |
| Space | O(h) recursion for DFS; O(w) queue for level order (w = widest level) |
- ✗Mixing the orders. Remember where the Node goes - pre = N L R, in = L N R, post = L R N.
- ✗Forgetting that "left before right" is the same in all three.
- ✗Using a stack for level order (that gives a DFS order instead).
Trees where a node can have any number of children, stored with only two pointers per node using the left-child/right-sibling representation.
| visit all nodes | O(n) |
| find the k-th child | O(k) |
| count the children of a node | O(degree) |
| Space | O(n), exactly two pointers per node |
- ✗Linking a node's second child to the parent instead of to the first child's
nextSibling. - ✗Drawing siblings as "right child of the parent".
- ✗Forgetting that the root has no siblings.
- Binary tree:
- A tree in which every node has at most two children, left and right.
- Root / leaf:
- The top node (no parent) / a node with no children.
- Depth / height:
- Depth: edges from the root down to a node. Height: edges from a node down to its deepest leaf.
- Full / complete / perfect:
- Full: 0 or 2 children everywhere. Complete: all levels full except the last, filled from the left. Perfect: all levels full.
- BST:
- Binary search tree: for every node, left subtree < node < right subtree.
- Inorder successor:
- The next value in sorted order; for a node with a right child, the minimum of its right subtree.
- Preorder / inorder / postorder:
- Depth-first traversals: Node-Left-Right / Left-Node-Right / Left-Right-Node.
- Level-order traversal:
- Visit a tree level by level using a queue (BFS on a tree).
- General tree:
- A tree whose nodes may have any number of children.
- Left-child, right-sibling:
- Stores a general tree with two pointers per node: first child and next sibling.
Graphs
dsa.fetehadin.comVertices and edges, adjacency, degree, paths, cycles, connected graphs, weighted graphs, and how a tree is a special graph.
| max edges, undirected | V(V − 1)/2 |
| max edges, directed | V(V − 1) |
| sum of all degrees | 2E (each edge counted at both ends) |
| Space | Depends on the representation (next topic) |
- ✗Counting path length in vertices instead of edges.
- ✗Saying every graph is connected.
- ✗Forgetting that a cycle must not reuse an edge.
Two ways to store a graph in memory, what each costs, and when to choose which.
| space | matrix O(V²) · list O(V + E) |
| is there an edge u–v? | matrix O(1) · list O(deg u) |
| visit all neighbours of u | matrix O(V) · list O(deg u) |
| add an edge | matrix O(1) · list O(1) |
- ✗Setting only
m[u][v]for an undirected edge. Setm[v][u]too. - ✗Saying the list is always better. Edge lookup is O(1) only in the matrix.
- ✗Forgetting that an undirected edge appears twice in the list.
One-way edges, in-degree and out-degree, directed paths and cycles, and how the matrix and list change.
| out-degree of u | list O(1) (size of adj[u]) · matrix O(V) (row sum) |
| in-degree of u | list O(V + E) · matrix O(V) (column sum) |
| space | matrix O(V²) · list O(V + E) |
- ✗Adding both
m[u][v]andm[v][u]for a directed edge. - ✗Following an arrow backwards when tracing a path.
- ✗Mixing up in-degree (arrows coming in) and out-degree (arrows going out).
Breadth-first search with a queue, depth-first search with recursion or a stack, the visited array, and O(V + E).
| BFS / DFS with adjacency list | O(V + E) |
| BFS / DFS with adjacency matrix | O(V²) |
| Space | O(V) for visited plus the queue or stack |
- ✗No visited array, so the search loops forever around a cycle.
- ✗In BFS, marking a vertex visited when it is dequeued instead of enqueued (it can be enqueued twice).
- ✗Visiting neighbours in a different order from the one the question uses (usually ascending).
- Graph:
- G = (V, E): a set of vertices and a set of edges between them.
- Degree:
- Number of edges at a vertex. In a digraph, split into in-degree and out-degree.
- Path / cycle:
- A sequence of vertices joined by edges / a path that returns to its start.
- Connected graph:
- There is a path between every pair of vertices.
- Adjacency matrix:
- V×V table where m[u][v] = 1 (or the weight) if there is an edge u→v. O(V²) space.
- Adjacency list:
- For each vertex, a list of its neighbours. O(V + E) space.
- Directed graph (digraph):
- A graph whose edges are one-way arrows.
- DAG:
- Directed acyclic graph: a digraph with no directed cycles.
- BFS:
- Breadth-first search: explore neighbours in waves using a queue. Finds fewest-edge paths.
- DFS:
- Depth-first search: go as deep as possible, then backtrack, using recursion or a stack.
Advanced Sorting & Searching
dsa.fetehadin.comInsertion sort on items a gap apart, with the gap shrinking to 1. Far-away items move in big jumps, so the final insertion sort has little work left.
| Best case (sorted) | O(n log n) |
| Average case | depends on the gaps; about O(n^1.5) for n/2, n/4, … |
| Worst case (gaps n/2, n/4, …) | O(n²) |
| Space | O(1) |
| Stable · in-place | no · yes |
- ✗Comparing neighbours (gap 1) in every pass, which is just insertion sort.
- ✗Forgetting that the last pass must use gap = 1, or the array may not be sorted.
- ✗Claiming it is stable. Jumps over a gap can reorder equal keys.
Pick a pivot, partition so smaller items go left and bigger items go right, then sort each side recursively. Average O(n log n), worst O(n²).
| Best case (pivot splits in half) | O(n log n) |
| Average case | O(n log n) |
| Worst case (sorted input, last-item pivot) | O(n²) |
| Space | O(log n) recursion on average, O(n) worst |
| Stable · in-place | no · yes |
- ✗Recursing on
(lo, p)instead of(lo, p − 1). The pivot is already in place. - ✗Forgetting the final
swap(a[i + 1], a[hi])that puts the pivot in place. - ✗Saying quick sort is always O(n log n).
Split the array in half, sort each half recursively, then merge the two sorted halves. Always O(n log n), stable, but needs O(n) extra space.
| Best case | O(n log n) |
| Average case | O(n log n) |
| Worst case | O(n log n) |
| Space | O(n) for the temporary array |
| Stable · in-place | yes · no |
- ✗Forgetting to copy the leftover items after one half runs out.
- ✗Using
<instead of<=in merge, which breaks stability. - ✗Base case
lo > hionly. A single item (lo == hi) must also stop the recursion.
Build a max-heap in the array, then repeatedly swap the root (the largest item) to the end and sift down. O(n log n) in every case, in-place.
| Build heap | O(n) |
| Each extract-max (swap + heapify) | O(log n) |
| Best / average / worst | O(n log n) |
| Space | O(1) iterative, O(log n) with recursive heapify |
| Stable · in-place | no · yes |
- ✗Using 1-based formulas (2i, 2i+1) with a 0-based array. With 0-based it is 2i+1 and 2i+2.
- ✗Starting the build loop at i = 0 instead of n/2 − 1, going down to 0.
- ✗Calling heapify on the whole array instead of the shrinking heap size
end.
A hash function turns a key into an array index, so search is O(1) on average. Collisions, separate chaining, linear probing and the load factor.
| search / insert / delete, average | O(1) |
| search / insert / delete, worst (all keys collide) | O(n) |
| Space | O(M + n) |
- ✗With linear probing, stopping a search at a slot holding a different key. Only an empty slot means "not found".
- ✗Forgetting to wrap around with
% Mwhen probing. - ✗Claiming hashing is always O(1). The worst case is O(n).
- Divide and conquer:
- Split a problem into smaller parts, solve them recursively, combine the results.
- Pivot:
- The item quick sort partitions around.
- Partition:
- Rearranging a range so items smaller than the pivot come before it and bigger ones after.
- Max-heap:
- A complete binary tree where every parent is ≥ its children; stored in an array.
- Heapify:
- Sift a node down until the heap property holds below it. O(log n).
- Gap (shell sort):
- The distance between compared items in a shell sort pass; shrinks to 1.
- Hash function:
- Maps a key to an index of the hash table, e.g. h(k) = k mod M.
- Collision:
- Two different keys hashing to the same index.
- Separate chaining:
- Each table slot holds a list of all keys that hash there.
- Linear probing:
- On a collision, try the next slot (h + i) % M until an empty one.
- Load factor:
- α = n / M, how full a hash table is.