MODULE 1

Two Pointers

Linear scan with two indices. Reduce O(n²) brute force to O(n).

When

  • Sorted array with target sum/diff.
  • Palindrome / reverse / partition in place.
  • Merge two sorted sequences.
  • Squared sorted array, dutch flag, remove duplicates.

Template

# opposing ends
def two_sum_sorted(a, target):
    l, r = 0, len(a) - 1
    while l < r:
        s = a[l] + a[r]
        if s == target: return [l, r]
        if s < target: l += 1
        else: r -= 1
    return []

# same direction (partition / dedup)
def remove_duplicates(a):
    w = 0
    for r in range(len(a)):
        if r == 0 or a[r] != a[r-1]:
            a[w] = a[r]; w += 1
    return w
MODULE 2

Sliding Window

Subarray / substring problems with monotonic constraint.

Fixed-Size

def max_sum_k(a, k):
    s = sum(a[:k]); best = s
    for i in range(k, len(a)):
        s += a[i] - a[i-k]
        best = max(best, s)
    return best

Variable

# longest substring with at most K distinct chars
def longest_k_distinct(s, k):
    cnt = {}; l = 0; best = 0
    for r, c in enumerate(s):
        cnt[c] = cnt.get(c, 0) + 1
        while len(cnt) > k:
            cnt[s[l]] -= 1
            if cnt[s[l]] == 0: del cnt[s[l]]
            l += 1
        best = max(best, r - l + 1)
    return best
MODULE 3

Fast / Slow Pointers

Cycle detect, midpoint, k-from-end on linked lists.

Floyd's Tortoise & Hare

def detect_cycle_start(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next; fast = fast.next.next
        if slow is fast: break
    else: return None
    slow = head
    while slow is not fast:
        slow = slow.next; fast = fast.next
    return slow
MODULE 4

Prefix Sum & Difference Array

O(1) range queries / range updates.

Prefix Sum

p = [0] * (len(a) + 1)
for i, v in enumerate(a): p[i+1] = p[i] + v
# range sum [l, r] = p[r+1] - p[l]

# subarray sum equals K (count)
def count_k(a, k):
    seen = {0: 1}; s = 0; ans = 0
    for v in a:
        s += v
        ans += seen.get(s - k, 0)
        seen[s] = seen.get(s, 0) + 1
    return ans

Difference Array (range update)

# add v to a[l..r] in O(1); reconstruct in O(n)
d = [0] * (n + 1)
def update(l, r, v):
    d[l] += v; d[r+1] -= v
def build():
    a = [0] * n; cur = 0
    for i in range(n):
        cur += d[i]; a[i] = cur
    return a
MODULE 5

Binary Search

Halve the search space. Variants: find first, last, on answer.

Classic

def lower_bound(a, x):
    l, r = 0, len(a)
    while l < r:
        m = (l + r) // 2
        if a[m] < x: l = m + 1
        else: r = m
    return l   # first index with a[i] >= x

Binary Search on Answer

# minimum capacity to ship within D days
def feasible(cap, weights, D):
    days, cur = 1, 0
    for w in weights:
        if cur + w > cap: days += 1; cur = 0
        cur += w
    return days <= D

def min_capacity(weights, D):
    lo, hi = max(weights), sum(weights)
    while lo < hi:
        m = (lo + hi) // 2
        if feasible(m, weights, D): hi = m
        else: lo = m + 1
    return lo
MODULE 6

BFS / DFS Templates

Graph + grid traversal. BFS = shortest unweighted path; DFS = explore all.

BFS

from collections import deque
def bfs(start, adj):
    dist = {start: 0}
    q = deque([start])
    while q:
        u = q.popleft()
        for v in adj[u]:
            if v not in dist:
                dist[v] = dist[u] + 1
                q.append(v)
    return dist

DFS

def dfs(u, adj, seen):
    seen.add(u)
    for v in adj[u]:
        if v not in seen: dfs(v, adj, seen)

# iterative
def dfs_iter(start, adj):
    stack = [start]; seen = {start}
    while stack:
        u = stack.pop()
        for v in adj[u]:
            if v not in seen:
                seen.add(v); stack.append(v)

Grid Walk

DIR = [(1,0),(-1,0),(0,1),(0,-1)]
def in_bounds(r, c, R, C): return 0 <= r < R and 0 <= c < C
# multi-source BFS for nearest-X distance, etc.
MODULE 7

Backtracking

Explore decision tree with prune. Subsets, permutations, N-queens, sudoku.

Template

def backtrack(path, choices):
    if is_solution(path):
        result.append(path[:]); return
    for c in choices:
        if not feasible(path, c): continue
        path.append(c)
        backtrack(path, next_choices(choices, c))
        path.pop()

Subsets / Permutations

def subsets(nums):
    out = []; cur = []
    def go(i):
        if i == len(nums): out.append(cur[:]); return
        go(i + 1)
        cur.append(nums[i]); go(i + 1); cur.pop()
    go(0); return out
MODULE 8

Topological Sort

DAG ordering. Course schedule, build deps, dataflow.

Kahn's BFS

from collections import deque
def topo(n, edges):
    adj = [[] for _ in range(n)]
    indeg = [0] * n
    for u, v in edges:
        adj[u].append(v); indeg[v] += 1
    q = deque(i for i in range(n) if indeg[i] == 0)
    order = []
    while q:
        u = q.popleft(); order.append(u)
        for v in adj[u]:
            indeg[v] -= 1
            if indeg[v] == 0: q.append(v)
    return order if len(order) == n else []   # [] = cycle
MODULE 9

Union-Find (DSU)

Connectivity, MST, dynamic groups. Path compression + union by rank → α(n).

Template

class DSU:
    def __init__(self, n):
        self.p = list(range(n)); self.r = [0] * n
    def find(self, x):
        while self.p[x] != x:
            self.p[x] = self.p[self.p[x]]   # path compression
            x = self.p[x]
        return x
    def union(self, a, b):
        ra, rb = self.find(a), self.find(b)
        if ra == rb: return False
        if self.r[ra] < self.r[rb]: ra, rb = rb, ra
        self.p[rb] = ra
        if self.r[ra] == self.r[rb]: self.r[ra] += 1
        return True
MODULE 10

Dynamic Programming Patterns

Subproblem + memo + recurrence. Identify state minimally.

1D DP — House Robber

def rob(a):
    take = skip = 0
    for v in a:
        take, skip = skip + v, max(take, skip)
    return max(take, skip)

0/1 Knapsack

def knap(W, wt, val):
    dp = [0] * (W + 1)
    for i in range(len(wt)):
        for w in range(W, wt[i] - 1, -1):
            dp[w] = max(dp[w], dp[w - wt[i]] + val[i])
    return dp[W]

LIS

from bisect import bisect_left
def lis(a):
    tails = []
    for x in a:
        i = bisect_left(tails, x)
        if i == len(tails): tails.append(x)
        else: tails[i] = x
    return len(tails)

Edit Distance

def edit(a, b):
    n, m = len(a), len(b)
    dp = [[0]*(m+1) for _ in range(n+1)]
    for i in range(n+1): dp[i][0] = i
    for j in range(m+1): dp[0][j] = j
    for i in range(1, n+1):
        for j in range(1, m+1):
            if a[i-1] == b[j-1]: dp[i][j] = dp[i-1][j-1]
            else: dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])
    return dp[n][m]

Interval DP

Burst balloons, matrix chain, palindrome partition. Iterate by length: for L in 2..n: for i in 0..n-L.

Bitmask DP

State = subset of n elements (n ≤ 20). TSP, assignments. dp[mask][i] = best ending at i covering mask.

MODULE 11

Greedy & Intervals

Local optimum → global. Sort + sweep.

Interval Scheduling

# max non-overlapping: sort by END
def max_intervals(iv):
    iv.sort(key=lambda x: x[1])
    end = -float('inf'); cnt = 0
    for s, e in iv:
        if s >= end: cnt += 1; end = e
    return cnt

# merge overlapping: sort by START
def merge(iv):
    iv.sort(); out = []
    for s, e in iv:
        if out and s <= out[-1][1]: out[-1][1] = max(out[-1][1], e)
        else: out.append([s, e])
    return out

Heap Greedy

Meeting rooms II, k closest, merge k sorted, dijkstra. Pattern: pop min, push derived.

MODULE 12

Monotonic Stack / Deque

Next greater / smaller, sliding window max.

Next Greater Element

def next_greater(a):
    n = len(a); res = [-1] * n; st = []
    for i in range(n):
        while st and a[st[-1]] < a[i]:
            res[st.pop()] = a[i]
        st.append(i)
    return res

Sliding Window Max

from collections import deque
def max_window(a, k):
    dq = deque(); out = []
    for i, v in enumerate(a):
        while dq and a[dq[-1]] <= v: dq.pop()
        dq.append(i)
        if dq[0] <= i - k: dq.popleft()
        if i >= k - 1: out.append(a[dq[0]])
    return out
MODULE 13

Bit Manipulation

XOR tricks, set ops, low-bit, counting.

Common Tricks

x & (x - 1)         # clear lowest set bit
x & -x              # isolate lowest set bit
bin(x).count('1')   # popcount
x ^ y               # set of differing bits

# single number (others appear twice)
def single(a):
    r = 0
    for v in a: r ^= v
    return r

# subset enumeration of bitmask m
sub = m
while sub:
    process(sub)
    sub = (sub - 1) & m
MODULE 14

Cheat Sheet

Pattern matcher: input shape → likely technique.

Signal → Pattern

  • Sorted array + target → two pointers / binary search
  • Subarray + condition → sliding window
  • Linked list cycle / midpoint → fast/slow
  • Range sum / count → prefix sum
  • Min/max with feasibility → BS on answer
  • "How many ways" → DP
  • Shortest path unweighted → BFS
  • All paths / permutations → backtracking
  • Course schedule / build order → topo sort
  • Connectivity / MST → Union-Find
  • Next greater / window max → monotonic stack/deque
  • Frequency / set ops → hashmap

Complexity Targets (1s)

  • n ≤ 12 → O(n!) OK
  • n ≤ 25 → O(2ⁿ) OK
  • n ≤ 100 → O(n⁴) OK
  • n ≤ 500 → O(n³) OK
  • n ≤ 5,000 → O(n²)
  • n ≤ 10⁶ → O(n log n)
  • n ≤ 10⁸ → O(n)

Edge Cases Always

  • Empty / single element
  • All same / all distinct
  • Negative numbers / overflow
  • Disconnected graph
  • Self-loop / cycle
  • k larger than input
  • Duplicates in keys

Python Stdlib

  • collections.Counter, deque, defaultdict
  • heapq (min-heap; negate for max)
  • bisect.bisect_left / right
  • itertools.combinations / permutations
  • functools.lru_cache for memo
  • sortedcontainers.SortedList O(log n)