Skip to content

Master Complexity Cheatsheet

Complexity is the one thing you’re expected to know cold, without looking it up mid-interview or mid-contest. This page is the single-place reference: every operation, data structure, and algorithm from this track, with its time and space complexity side by side. Treat it as a lookup table, not a lesson — if a row doesn’t make sense, the phase that taught it is linked from context in earlier pages.

  • The real complexity of every common list / dict / set / deque / heapq operation — including the ones that silently cost O(n)O(n).
  • Every sorting and searching algorithm from Phase 4, side by side.
  • Every data structure’s core operations, from arrays to segment trees, Fenwick trees, and DSU.
  • The graph algorithm family — BFS, DFS, Dijkstra, Bellman-Ford, Floyd-Warshall, MST, and topological sort.
  • The Big-O growth hierarchy, and a rule of thumb for how large n can be under a typical time limit.
Operationlistdictsetdequeheapq
Index / key access x[i]O(1)O(1)O(1)O(1) avgO(n)O(n)
Append / addO(1)O(1) amortizedO(1)O(1) avgO(1)O(1) avgO(1)O(1)O(logn)O(\log n) (heappush)
Append/insert at frontO(n)O(n)O(1)O(1)
Pop from endO(1)O(1)O(1)O(1)
Pop from frontO(n)O(n)O(1)O(1)O(logn)O(\log n) (heappop, smallest only)
Insert / delete at arbitrary indexO(n)O(n)O(1)O(1) avg (del d[k])O(1)O(1) avgO(n)O(n)not supported directly
Membership test x in ...O(n)O(n)O(1)O(1) avgO(1)O(1) avgO(n)O(n)O(n)O(n)
Search for min/maxO(n)O(n)O(n)O(n)O(n)O(n)O(n)O(n)O(1)O(1) (min only, heap[0])
Sort (sorted(...), .sort())O(nlogn)O(n \log n)O(nlogn)O(n \log n) on itemsO(nlogn)O(n \log n) on itemsO(nlogn)O(n \log n)O(nlogn)O(n \log n) (heapify + drain)
sorted(list) from scratchBuilding via heapify is O(n)O(n)
AlgorithmBestAverageWorstSpaceStable?Notes
Timsort (Python’s sorted/.sort())O(n)O(n)O(nlogn)O(n \log n)O(nlogn)O(n \log n)O(n)O(n)YesAdaptive — exploits existing runs of order
Merge SortO(nlogn)O(n \log n)O(nlogn)O(n \log n)O(nlogn)O(n \log n)O(n)O(n)YesGuaranteed O(nlogn)O(n \log n), no worst-case cliff
Quick SortO(nlogn)O(n \log n)O(nlogn)O(n \log n)O(n2)O(n^2)O(logn)O(\log n)NoWorst case needs an adversarial/sorted input plus a bad pivot rule
Heap SortO(nlogn)O(n \log n)O(nlogn)O(n \log n)O(nlogn)O(n \log n)O(1)O(1)NoIn-place, but poor cache locality in practice
Insertion SortO(n)O(n)O(n2)O(n^2)O(n2)O(n^2)O(1)O(1)YesBest-in-class for nearly-sorted or tiny arrays
Selection SortO(n2)O(n^2)O(n2)O(n^2)O(n2)O(n^2)O(1)O(1)NoAlways O(n2)O(n^2) comparisons, minimal writes
Bubble SortO(n)O(n)O(n2)O(n^2)O(n2)O(n^2)O(1)O(1)YesTeaching tool only — never the right production choice
Counting SortO(n+k)O(n + k)O(n+k)O(n + k)O(n+k)O(n + k)O(n+k)O(n + k)Yesk = range of key values; useless when k >> n
Radix SortO(d(n+k))O(d(n + k))O(d(n+k))O(d(n + k))O(d(n+k))O(d(n + k))O(n+k)O(n + k)Yesd = number of digits/passes
Bucket SortO(n+k)O(n + k)O(n+k)O(n + k)O(n2)O(n^2)O(n)O(n)Yes (with a stable inner sort)Needs a roughly-uniform input distribution
TechniqueTimeSpaceRequires
Linear searchO(n)O(n)O(1)O(1)Nothing
Binary search (exact match)O(logn)O(\log n)O(1)O(1)Sorted array
lower_bound / upper_bound (bisect)O(logn)O(\log n)O(1)O(1)Sorted array
Binary search on the answerO(f(n)log(range))O(f(n) \log(\text{range}))Depends on feasible()Monotonic feasibility
Hash-based lookup (dict/set)O(1)O(1) average, O(n)O(n) worstO(n)O(n)Hashable keys
Two pointers / sliding windowO(n)O(n)O(1)O(1)Sorted or monotonic structure
StructureAccessSearchInsertDeleteSpaceNotes
Array / Python listO(1)O(1)O(n)O(n)O(n)O(n) worst (end: O(1)O(1) amortized)O(n)O(n)O(n)O(n)Contiguous, cache-friendly
Singly/doubly linked listO(n)O(n)O(n)O(n)O(1)O(1) at a known nodeO(1)O(1) at a known nodeO(n)O(n)No random access; cheap splicing
Stack (list as stack)O(n)O(n)O(n)O(n)O(1)O(1) (append)O(1)O(1) (pop)O(n)O(n)LIFO — both ops at the same end
Queue (collections.deque)O(n)O(n)O(n)O(n)O(1)O(1) (append)O(1)O(1) (popleft)O(n)O(n)FIFO — never use list.pop(0) here
Hash table (dict/set)O(1)O(1) avg, O(n)O(n) worstO(1)O(1) avgO(1)O(1) avgO(n)O(n)Worst case needs adversarial hash collisions
Binary heap (heapq)O(1)O(1) (min only)O(n)O(n)O(logn)O(\log n)O(logn)O(\log n) (min)O(n)O(n)heapify builds from scratch in O(n)O(n)
Binary Search Tree (unbalanced)O(n)O(n) worstO(n)O(n) worstO(n)O(n) worstO(n)O(n) worstO(n)O(n)Degenerates to a linked list on sorted input
Balanced BST (AVL / Red-Black)O(logn)O(\log n)O(logn)O(\log n)O(logn)O(\log n)O(logn)O(\log n)O(n)O(n)Guarantees hold even on adversarial input
TrieO(L)O(L)O(L)O(L)O(L)O(L)O(L)O(L)O(total chars)O(\text{total chars})L = length of the key/word
Segment TreeO(logn)O(\log n) query/updateO(logn)O(\log n) updateO(n)O(n)O(n)O(n) build; lazy propagation keeps range updates O(logn)O(\log n)
Fenwick Tree (BIT)O(logn)O(\log n) queryO(logn)O(\log n) updateO(n)O(n)Simpler and faster in practice than a segment tree for prefix sums
Disjoint Set Union (DSU)O(α(n))O(\alpha(n)) findO(α(n))O(\alpha(n)) unionO(n)O(n)With path compression + union by rank/size; α\alpha is the inverse Ackermann function — effectively constant
AlgorithmTimeSpaceAnswers
BFSO(V+E)O(V + E)O(V)O(V)Shortest path in an unweighted graph, level order, multi-source distance
DFSO(V+E)O(V + E)O(V)O(V)Reachability, cycle detection, topological order, connected components
Topological sort (Kahn’s or DFS-based)O(V+E)O(V + E)O(V)O(V)A valid linear order of a DAG
Dijkstra (binary heap)O((V+E)logV)O((V + E) \log V)O(V)O(V)Shortest path, non-negative weights only
Bellman-FordO(VE)O(V \cdot E)O(V)O(V)Shortest path with negative weights; also detects negative cycles
Floyd-WarshallO(V3)O(V^3)O(V2)O(V^2)All-pairs shortest paths, works with negative weights (no negative cycles)
Prim’s MST (binary heap)O(ElogV)O(E \log V)O(V)O(V)Minimum spanning tree, dense-ish graphs
Kruskal’s MST (with DSU)O(ElogE)O(E \log E)O(V)O(V)Minimum spanning tree, sparse graphs / edge lists

From fastest-growing-slowly to fastest-growing-explosively:

O(1)<O(logn)<O(n)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)O(1) < O(\log n) < O(\sqrt{n}) < O(n) < O(n \log n) < O(n^2) < O(n^3) < O(2^n) < O(n!)
ComplexityNameFeels like
O(1)O(1)ConstantA dict lookup, an array index
O(logn)O(\log n)LogarithmicBinary search, balanced BST operations
O(n)O(\sqrt{n})RootTrial-division primality check, sparse tables’ build-adjacent factor
O(n)O(n)LinearA single pass — linear search, array sum
O(nlogn)O(n \log n)LinearithmicComparison-based sorting, most divide-and-conquer
O(n2)O(n^2)QuadraticNested loops over the same input — naive pair comparisons
O(n3)O(n^3)CubicTriple-nested loops — Floyd-Warshall, naive matrix multiply
O(2n)O(2^n)ExponentialEnumerating every subset, unmemoized exponential recursion
O(n!)O(n!)FactorialEnumerating every permutation
sketch Where each complexity crosses the one-second budget p5.js
Log-log axes: the horizontal axis is n from 1 to 10^9, the vertical is operation count from 1 to 10^18. The dashed line is the 10^8 budget from the table above. What matters is not the slope of any curve but where it crosses that line -- that crossing IS the maximum n the complexity can handle. Every crossing is solved on the fly by bisection, not read from a table.

Contest judges typically allow roughly 10810^8-10910^9 simple operations per second in Python (often with a looser limit than C++ for exactly this reason). Working backward from a 1-2 second limit gives a rough ceiling on n for each complexity class:

ComplexityComfortable max n
O(n!)O(n!)n10n \le 10-ish
O(2n)O(2^n)n20n \le 20-25
O(n3)O(n^3)n300n \le 300-500
O(n2)O(n^2)n5,000n \le 5{,}000-10,000
O(nlogn)O(n \log n)n106n \le 10^6-10710^7
O(n)O(n)n107n \le 10^7-10810^8
O(logn)O(\log n) / O(1)O(1)nn up to 101810^{18} (the input size barely matters)

Self-check — reading a constraint. Given n2000n \le 2000 and a 1-second limit, complete the comparison that decides whether an O(n2)O(n^2) solution fits comfortably.

  • Treat it as a lookup, not reading material — bookmark it and come back mid-problem when you need to double-check a complexity before committing to an approach.
  • When a problem gives you n and a time limit, use the “max n per time limit” table to shortlist which complexity classes are even in the running before you design an algorithm.
  • When two data structures both “work,” pick the one whose row here has the better guarantee for the operation your problem calls the most (e.g. a Fenwick tree over a segment tree when you only need prefix sums, not range updates).

Read the constraint, name the intended complexity

Section titled “Read the constraint, name the intended complexity”
sketch Read the constraint, name the complexity p5.js
The constraint in the problem statement tells you the intended complexity, and this is the arithmetic that turns one into the other. n sweeps upward; for each complexity the bar shows the operation count against the 10^8 budget, and a class flips from green to red the moment it no longer fits. The rightmost class still green is the answer to give. Counts are computed live, so n = 2000 shows exactly the 4,000,000 the page states for O(n^2).

The one table this whole page exists to support — what fits in about one second, at roughly 10810^8 simple operations:

Max nLargest complexity that fitsTypical approach
<= 12O(n!)O(n!)permutations, brute-force TSP
<= 25O(2n)O(2^n)subsets, bitmask DP
<= 500O(n3)O(n^3)Floyd-Warshall, interval DP
<= 5,000O(n2)O(n^2)pairwise DP, LCS, edit distance
<= 10^5-10^6O(nlogn)O(n \log n)sorting, heaps, segment trees, binary search on the answer
<= 10^7O(n)O(n)one pass, sliding window, prefix sums
>= 10^9O(logn)O(\log n) / O(1)O(1)binary search on the answer, closed form, digit DP

Two adjustments that matter in practice:

  • Python’s constant factor is roughly 10-100x C++. Shift the table down about one order of magnitude for pure-Python loops — 10710^7 simple operations per second is a safer working figure than 10810^8. Work pushed into C (sort, sum, bytes, set operations, NumPy) does not pay that tax.
  • Read the bound’s variable. “Binary search on the answer” is O(nlogR)O(n \log R) where RR is the value range, not n; a pseudo-polynomial DP like knapsack is O(nW)O(nW) in the capacity. Quoting either as a function of n alone is the usual mistake, and it is what makes a solution that looks fine time out.
They askWhat they’re checkingThe answer
“The constraint is n <= 20. What does that tell you?”Reading constraints as hintsThat exponential in n is expected — 2202^{20} is about a million. A bound that small is almost always pointing at a bitmask or a full enumeration
“What is the complexity of list.insert(0, x)?”Whether you know the data modelO(n)O(n) — every element shifts. That is why a queue uses collections.deque, where both ends are O(1)O(1)
in on a list versus a set?”The most common Python performance bugO(n)O(n) versus O(1)O(1) average. A membership test inside a loop turns an O(n)O(n) algorithm into O(n2)O(n^2) without changing a line of the logic
“Amortised versus worst case?”PrecisionA list.append is O(1)O(1) amortised and O(n)O(n) on the resize that copies. Over n appends the total is O(n)O(n), so amortised is the honest figure to quote for a loop
“Your solution is O(nlogn)O(n \log n) but times out”Diagnosing beyond the classEither the constant is the problem — Python-level loops, per-iteration allocation, string concatenation in a loop — or the bound’s variable is not what you assumed. Check both before rewriting the algorithm
“Space complexity of your recursion?”The invisible costO(h)O(h) stack frames, which is O(n)O(n) on a degenerate input and invisible in the source. It is also why CPython’s ~1000-frame limit turns some correct solutions into crashes
“Sorting is O(nlogn)O(n \log n) — can you ever beat it?”Knowing the modelOnly by leaving the comparison model: counting or radix sort is O(n+k)O(n + k) for small integer keys. The Ω(nlogn)\Omega(n \log n) bound applies to comparison sorts specifically
“Which of these bounds do people quote wrongly?”JudgementPseudo-polynomial ones — knapsack’s O(nW)O(nW) and digit DP’s — because they are polynomial in a value rather than an input length. Naive Fibonacci is O(ϕn)O(\phi^n), not O(2n)O(2^n)

Problems whose intended solution is decided by a complexity budget rather than a trick. For each, work out the target complexity from the constraints before looking at the tags.

29 problems
8 easy16 medium5 hard

Work down the ladder. Tick each problem off as you go — progress is saved in this browser, and the Export button in the filter bar writes it to a file you can keep.

  • The budget — about 10810^8 simple operations per second, and an order of magnitude fewer in pure Python. Do this arithmetic before choosing an approach.
  • Constraint to complexityn <= 12: O(n!)O(n!) · <= 25: O(2n)O(2^n) · <= 500: O(n3)O(n^3) · <= 5000: O(n2)O(n^2) · <= 10^6: O(nlogn)O(n \log n) · <= 10^7: O(n)O(n) · beyond: O(logn)O(\log n) or a formula.
  • Python data modellist.insert(0, ...) and list.pop(0) are O(n)O(n); deque is O(1)O(1) at both ends. x in list is O(n)O(n), x in set is O(1)O(1) — the most common accidental O(n2)O(n^2).
  • append is O(1)O(1) amortised, O(n)O(n) on the resize. Quote the amortised figure for a loop.
  • Recursion costs O(h)O(h) space and dies at ~1000 frames in CPython.
  • Read the bound’s variableO(nW)O(nW) and O(nlogR)O(n \log R) are polynomial in a value, not a length. That is what makes a “fine” solution time out.
  • Beating O(nlogn)O(n \log n) requires leaving the comparison model: counting or radix sort.
  • Python’s built-in containers hide real complexity costs — list.pop(0) and list.insert(0, x) are O(n)O(n), not O(1)O(1); use deque when you need fast operations at both ends.
  • Timsort’s O(nlogn)O(n \log n) worst case and O(n)O(n) best case (on already-sorted runs) is why sorted()/.sort() is almost always the right call over a hand-rolled sort.
  • Balanced BSTs, Fenwick trees, and segment trees all trade a bit of extra bookkeeping for a guaranteed O(logn)O(\log n) where a naive structure would degrade to O(n)O(n) on adversarial input.
  • Shortest-path algorithm choice is driven entirely by what the graph allows: unweighted -> BFS, non-negative weights -> Dijkstra, negative weights -> Bellman-Ford, all-pairs on a small graph -> Floyd-Warshall.
  • The Big-O hierarchy plus a rough “operations per second” budget is enough to sanity-check almost any algorithm choice before you write a line of code.

Next: Phase 10 — Interview & Contest Strategy, where these templates and complexity guarantees get applied under real time pressure: how to plan an approach, communicate it, and choose the right tool fast when the clock is running.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading