Skip to main content
Strong algorithm knowledge separates engineers who can solve a problem from those who can solve it efficiently under constraints. This page covers the patterns that appear most frequently in both competitive programming and real system design: dynamic programming in its several forms, graph traversal and shortest paths, greedy reasoning, and mathematical foundations like modular inverse and fast exponentiation. All code examples are in C++ and come directly from the source notes.

Dynamic Programming Patterns

Dynamic programming (DP) solves optimization and counting problems by breaking them into overlapping subproblems, solving each once, and storing the results. The key insight is that optimal substructure holds: the optimal solution to the whole problem can be built from optimal solutions to subproblems.

Classic DP

0/1 Knapsack: given n items with weights and values, and a capacity W, choose items to maximize total value without exceeding W.
Grouped Knapsack: items come in groups; you pick at most one item per group.
Longest Common Subsequence (LCS): length of the longest subsequence common to two strings.
Longest Increasing Subsequence (LIS): length of the longest strictly increasing subsequence. Naive DP is O(n²); the patience sort / binary search optimization brings it to O(n log n).

Digit DP

Digit DP counts integers in a range [a, b] that satisfy a digit-level constraint (e.g., digits are non-decreasing, digit sum divisible by m). The standard template uses a dfs(pos, state, limit) function with memoization. Core idea: build the number digit by digit from the most significant position. At each position you may place any digit from 0 to 9 (or 0 to A[pos] if you are still tight against the upper bound). The limit flag tracks whether you are still constrained by the upper bound. Critical rule: you can only memoize a state when limit == false. When limit == true the valid digit range is restricted to A[pos], so the same (pos, state) pair may have a different answer depending on the path taken—memoizing it would produce wrong results.
A variant that tracks digit sum modulo mod:
If you accidentally write if (limit) instead of if (!limit) when checking whether to skip the memoization update, the cache will still produce correct values—but the time complexity degrades from polynomial to factorial because the pruning never fires.

Tree DP

Tree DP defines state on each subtree and transitions up to the parent via DFS. The standard form uses f[u][0] / f[u][1] to represent two choices at each node (e.g., selected / not selected in an independent set).
Exchange root DP (also called rerooting): when you need to answer queries rooted at every vertex, run one DFS downward then one DFS upward, propagating information from subtrees into the parent’s “complement subtree.”

Interval DP

Interval DP solves problems over contiguous subarrays. The outer loop iterates over interval length; the inner loop over starting positions; the innermost loop over split points.
Classic applications: matrix chain multiplication, optimal BST, burst balloons, palindrome partitioning.

Graph Algorithms

BFS and DFS

BFS explores a graph level by level using a queue, finding shortest paths by hop count in O(V + E). DFS explores deeply before backtracking using a stack (or recursion), useful for topological order, cycle detection, and generating all paths. Multi-source BFS: initialize the queue with multiple source nodes simultaneously. All sources start at distance 0. This solves “nearest X to each cell” problems in a single BFS pass.

Dijkstra’s Algorithm

Dijkstra finds the single-source shortest paths in a graph with non-negative edge weights. It uses a greedy strategy: always relax the unvisited node with the smallest current distance.
Dijkstra fails when negative edges exist. Use Bellman-Ford or SPFA instead.

Floyd-Warshall Algorithm

Floyd-Warshall computes all-pairs shortest paths in O(V³) using DP over intermediate nodes.
After the triple loop, dist[x][y] holds the shortest path between every pair. Floyd-Warshall handles negative edges but not negative cycles. Run it on dense graphs or when you need all-pairs results.

Topological Sort (Kahn’s Algorithm)

Applications: build dependency resolution, course scheduling, detecting cycles in directed graphs.

Greedy Algorithms

A greedy algorithm makes a locally optimal choice at each step, hoping to find a global optimum. Greedy works when the greedy choice property holds (a locally optimal choice leads to a globally optimal solution) and the problem has optimal substructure.

Activity Selection / Maximum Non-Overlapping Intervals

Given intervals, select the maximum number of non-overlapping intervals. Greedy rule: always pick the interval that ends earliest. Sort by end time; scan left to right, adding an interval only if its start is ≥ the end of the last selected interval.
Time: O(n log n) for sorting, O(n) for the scan.

Invariant / Potential-Based Greedy

Some greedy problems require finding a non-obvious invariant. A classic example: given two arrays A and B that can be transformed via adjacent swaps, determine whether A can become B and find the minimum number of swaps. Key insight: define the potential (index + value) array. Arrays A and B can be transformed into each other if and only if their potential multisets are equal. To minimize swaps, use a greedy “nearest first” strategy combined with a BIT (Binary Indexed Tree / Fenwick Tree) to count inversions efficiently.

Common Greedy Patterns


Mathematical Algorithms

Fast Exponentiation (Binary Exponentiation)

Computes base^exp mod m in O(log exp) time by repeatedly squaring.

Modular Inverse

The modular inverse of a mod m is the value x such that a * x ≡ 1 (mod m). It exists when gcd(a, m) = 1. When m is prime, Fermat’s little theorem gives: a^(m-1) ≡ 1 (mod m), so the inverse is a^(m-2) mod m:
For non-prime moduli, use the extended Euclidean algorithm.

Matrix Exponentiation

When a recurrence can be expressed as matrix multiplication, fast exponentiation on matrices reduces O(n) DP to O(m³ log n) where m is the state dimension. Fibonacci example: the Fibonacci recurrence F(n) = F(n-1) + F(n-2) maps to:
The same technique applies to any linear recurrence, to counting paths in a graph in exactly k steps, and to computing sums of recurrences modulo a prime.

GCD and LCM

Combinatorics with Precomputed Factorials

For problems requiring C(n, k) mod p frequently, precompute factorials and inverse factorials:
This gives O(1) per query after O(n) precomputation, which is necessary for problems with up to 10^6 combinatorial queries.