Algorithms

The general algorithmic patterns, organized into eighteen topics where each one builds on the techniques of the ones before it. The synopsis below sketches each pattern in Python, and the topic pages underneath work through the problems built on it, with solutions side by side in Python, C++, Rust, TypeScript, Go, and Swift behind a language switcher. Every topic page now opens with an interactive visualizer that plays the algorithm through real test cases one step at a time, and a solution is published only after its implementation passes unit tests against the problem's published examples.

Featured article

Graph traversal, from first principles

A long-form walkthrough of breadth-first and depth-first search and the canonical problems built on them, from flood fill and islands to topological sort, with the reasoning worked out step by step.

Read the article →
New

Top Interview 150, the missing 48

The problems from LeetCode's Top Interview 150 study plan that the topic pages below don't already cover, each solved in all six languages as an approach ladder: a stepper walks every solution from a naive baseline up to the optimal one.

Work the ladder →

The patterns at a glance

Every problem on this page is an instance of one of eighteen patterns. Each card sketches the pattern's canonical shape in Python and links to its topic below.

Arrays & Hashing

Replace a rescan with something remembered. A set, a count table, or a prefix sum turns quadratic probing into one pass.

seen = {}
for i, x in enumerate(nums):
    if target - x in seen:
        return [seen[target - x], i]
    seen[x] = i

Two Pointers

On sorted or mirrored input, two indexes moving toward each other safely discard a candidate at every step.

lo, hi = 0, len(nums) - 1
while lo < hi:
    s = nums[lo] + nums[hi]
    if s < target: lo += 1
    elif s > target: hi -= 1
    else: return [lo, hi]

Sliding Window

Grow on the right, shrink on the left, and update state incrementally so no substring costs a fresh scan.

left, best, seen = 0, 0, set()
for right, ch in enumerate(s):
    while ch in seen:
        seen.remove(s[left]); left += 1
    seen.add(ch)
    best = max(best, right - left + 1)

Stack

A stack holds exactly the prefix still unresolved, and the monotonic variants pop everything the current element settles.

stack, ans = [], [0] * len(temps)
for i, t in enumerate(temps):
    while stack and temps[stack[-1]] < t:
        j = stack.pop(); ans[j] = i - j
    stack.append(i)

Binary Search

Any monotonic yes/no boundary can be halved, including answer spaces that are not arrays at all.

lo, hi = 0, len(nums)
while lo < hi:
    mid = (lo + hi) // 2
    if nums[mid] < target: lo = mid + 1
    else: hi = mid

Linked List

Pointer surgery behind a dummy head for edits, and two speeds of traversal for middles and cycle detection.

slow = fast = head
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next

Trees

Recursion mirrors the structure. Solve each subtree, combine at the root, and choose the traversal order that fits.

def depth(node):
    if not node:
        return 0
    return 1 + max(depth(node.left),
                   depth(node.right))

Heap / Priority Queue

When only the current best matters, a heap serves it in logarithmic time without keeping everything sorted.

h = nums[:k]
heapq.heapify(h)
for x in nums[k:]:
    if x > h[0]:
        heapq.heapreplace(h, x)

Backtracking

Build a candidate one choice at a time, recurse, then undo the choice so one path's memory serves every branch.

def backtrack(path, rest):
    if not rest:
        out.append(path[:]); return
    for i, c in enumerate(rest):
        path.append(c)
        backtrack(path, rest[:i] + rest[i+1:])
        path.pop()

Tries

A tree keyed by characters, so words sharing a prefix share a path and prefix queries fall out for free.

node = root
for ch in word:
    node = node.setdefault(ch, {})
node["end"] = True

Graphs

Breadth-first search with a queue finds shortest hop counts, while depth-first search finds components and cycles.

q, seen = deque([start]), {start}
while q:
    u = q.popleft()
    for v in adj[u]:
        if v not in seen:
            seen.add(v); q.append(v)

Advanced Graphs

Weights change the tools. Dijkstra's greedy frontier, union-find for connectivity, topological order for dependencies.

dist, pq = {src: 0}, [(0, src)]
while pq:
    d, u = heapq.heappop(pq)
    for v, w in adj[u]:
        if d + w < dist.get(v, inf):
            dist[v] = d + w
            heapq.heappush(pq, (d + w, v))

1-D Dynamic Programming

Define the answer for a prefix, write the recurrence, and fill the table so every subproblem is solved exactly once.

dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n + 1):
    dp[i] = dp[i - 1] + dp[i - 2]

2-D Dynamic Programming

Two dimensions of state, usually positions in two sequences or a grid, filled in an order where dependencies are ready.

for i in range(1, m):
    for j in range(1, n):
        dp[i][j] = grid[i][j] + min(
            dp[i - 1][j], dp[i][j - 1])

Greedy

Take the locally best step, backed by an exchange argument that no future regret is possible.

best = cur = nums[0]
for x in nums[1:]:
    cur = max(x, cur + x)
    best = max(best, cur)

Intervals

Sort by start, then merge or count overlaps in one sweep. Most interval problems are a sort plus one pass.

ivs.sort()
merged = [ivs[0]]
for s, e in ivs[1:]:
    if s <= merged[-1][1]:
        merged[-1][1] = max(merged[-1][1], e)
    else:
        merged.append([s, e])

Math & Geometry

In-place matrix tricks, modular arithmetic, and the handful of identities that turn simulation into formula.

matrix[:] = [list(r) for r in zip(*matrix)]
for row in matrix:
    row.reverse()

Bit Manipulation

Treat an integer as a set of flags. Clearing the lowest set bit and XOR's self-cancellation carry most problems.

count = 0
while n:
    n &= n - 1
    count += 1

What changes across languages

Writing every solution in six languages is mostly a lesson in what stays the same. The algorithm, the invariants, and the complexity are identical everywhere. What changes is what each language makes you say out loud. Rust is the strictest teacher. Reversing a linked list becomes an exercise in ownership, where each node is an Option<Box<ListNode>> and the idiom is to take() a node out of its slot, rewire it, and hand it back, because the borrow checker forbids the casual pointer aliasing a C solution leans on. Its standard BinaryHeap is a max-heap, so a min-heap wraps every entry in Reverse, strings refuse direct indexing until you commit to bytes or chars, and debug builds panic on integer overflow, which turns the lo + (hi - lo) / 2 midpoint from folklore into enforced discipline.

Go pushes in the opposite direction, toward writing the machinery yourself. There is no set type, so membership tests use map[T]struct{}, whose empty-struct values occupy zero bytes. A heap is not a container you import but an interface you implement, five methods on your own slice type, with the pop idiom slicing the last element off after the library swaps it into place. Indexing a string yields raw UTF-8 bytes while ranging over it yields runes, a distinction that decides whether a palindrome check is correct, and sorting takes a closure through sort.Slice rather than a comparator object.

C++, TypeScript, and Swift sit between those poles, with iterator invalidation and reference semantics in C++, TypeScript's single number type quietly making 64-bit bit manipulation hazardous, and Swift's value-semantic arrays with copy-on-write, which make in-place tricks subtler than they look. The topic pages call these differences out where they bite.

01

Arrays & Hashing

The foundation everything else builds on. Most optimal solutions here replace a rescan with something remembered, such as a hash set, a frequency table, a prefix sum, or a canonical key that makes equal things look equal.

02

Two Pointers

When the input is sorted or mirrored, two indexes that only move toward each other can replace a full pass over every pair, cutting quadratic work to linear.

03

Sliding Window

A window over the array grows on the right and shrinks on the left while its state updates incrementally, so every substring question stops costing a fresh scan.

04

Stack

A stack remembers exactly the prefix that is still unresolved. The monotonic variants answer next-greater and span questions in one pass by popping everything the current element settles.

06

Linked List

Pointer rewiring under constraints. Dummy heads remove edge cases, fast and slow pointers find middles and cycles, and the two cache designs are the classic exercises in composing data structures.

07

Trees

Nearly every tree problem is one recursion shape. Return a fact about each subtree once, combine the children's answers, and keep a separate best when the answer is not the return value.

08

Heap / Priority Queue

A heap keeps only what matters, the top k of a stream, the next event by time, or the boundary between the lower and upper half of the data.

09

Backtracking

One template generates the whole topic. Choose, recurse, undo. The problems differ only in what a choice is, what prunes a branch, and when to record a result.

10

Tries

A prefix tree turns whole-word lookups into per-character walks, which is what makes searching a grid for hundreds of words at once tractable.

11

Graphs

BFS for distances, DFS for structure, indegrees for ordering, and union-find for connectivity. The twelve canonical traversal variants are written out in six languages on the graph traversal page.

12

Advanced Graphs

Weighted edges change the rules. Dijkstra and its max-edge variants, minimum spanning trees, Bellman-Ford under a stop budget, and Eulerian paths.

13

1-D Dynamic Programming

Name the subproblem in one sentence, write the transition, and fill states in dependency order. These are the linear-state problems where that habit gets built.

14

2-D Dynamic Programming

Two indexes of state. Prefix pairs for string alignment, intervals for games and balloons, and grids where the path itself is the state.

15

Greedy

A greedy solution stands on an exchange argument. The locally best choice never blocks a globally best one. Each problem here teaches a distinct invariant worth saying out loud before coding.

16

Intervals

Sort by the boundary that controls conflicts, then sweep once. Sorting by end time solves scheduling, and sorting by start time solves merging.

17

Math & Geometry

Simulation problems where the win is finding the exact numeric invariant. Matrix layers, carries, modular identities, and exponentiation by squaring.

18

Bit Manipulation

A small toolbox that shows up everywhere. XOR cancellation, clearing the lowest set bit, and building answers bit by bit from the top.