PARETO REFERENCE · v1.0

Python DSA
Syntax Atlas

Every pattern, method, and gotcha needed to solve ~90% of DSA problems — in one searchable page. Built for fast lookup while grinding problems or prepping for interviews.

18Core Topics
120+Code Snippets
90%Coverage
O(1)Lookup Time
01

Lists & Arrays

Python's dynamic array. The workhorse of DSA — think of it as a row of labeled boxes where you can add, remove, or peek at any box in O(1) at the ends.

Creation Patterns

lists.py
# Empty / pre-filled
arr = []
arr = [0] * n                    # n zeros (1D)
arr = [0] * (rows * cols)        # flat 2D (rare)

# 2D matrix — ALWAYS use comprehension (avoid shared rows!)
matrix = [[0] * cols for _ in range(rows)]

# From iterables
arr = list(range(n))             # [0, 1, ..., n-1]
arr = list(map(int, input().split()))
arr = list("abc")                # ['a', 'b', 'c']
arr = [int(x) for x in input().split()]
Gotcha [[0]*cols]*rows creates rows aliases of the same list. Mutating one row mutates all. Always use the comprehension form.

Indexing & Slicing

Visualization
  arr = [10, 20, 30, 40, 50]
  idx:   0   1   2   3   4      (positive)
  neg:  -5  -4  -3  -2  -1      (negative)

  arr[1:4]      -> [20, 30, 40]     # start inclusive, end exclusive
  arr[:3]       -> [10, 20, 30]     # from beginning
  arr[2:]       -> [30, 40, 50]     # to end
  arr[-2:]      -> [40, 50]          # last 2
  arr[::2]      -> [10, 30, 50]     # step=2
  arr[::-1]     -> [50, 40, 30, 20, 10]   # reverse!
  arr[1:4:2]    -> [20, 40]          # start:stop:step

Methods — In-place vs New

methods.py
# ---- In-place (mutate original) ----
arr.append(x)          # O(1) amortized
arr.extend(iterable)   # O(k)
arr.insert(i, x)       # O(n) — shifts
arr.pop()              # O(1) — remove last
arr.pop(i)             # O(n) — shifts
arr.remove(x)          # O(n) — first match by value
arr.clear()            # O(n)
arr.sort()             # O(n log n)
arr.reverse()          # O(n)

# ---- Return new (don't mutate) ----
arr + other            # concatenation
arr * k                # repeat k times
sorted(arr)            # new sorted list
arr.copy()             # shallow copy (= arr[:])
arr.index(x)           # first index of x, ValueError if missing
arr.count(x)           # occurrences
len(arr)               # length
x in arr               # O(n) membership

List Comprehensions

comprehension.py
# Basic: [expr for var in iter if cond]
squares = [x*x for x in range(10)]
evens   = [x for x in arr if x % 2 == 0]

# With function call
upper = [s.upper() for s in words]

# Nested (2D)
flat = [x for row in matrix for x in row]

# Conditional expression
label = ['even' if x % 2 == 0 else 'odd' for x in arr]

# Multiple vars from pairs
keys = [k for k, v in pairs if v > 0]
When to use Readability. If the comprehension wraps multiple lines or nests 3+ levels, switch to a regular for loop.

Built-in Aggregators

FunctionReturnsExample
len(arr)O(1)size
sum(arr)O(n)total
min(arr) / max(arr)O(n)extreme
any(arr)O(n) short-circuitany truthy?
all(arr)O(n) short-circuitall truthy?
sorted(arr)O(n log n)new sorted list
enumerate(arr)O(1)(index, value) pairs
zip(a, b)O(1)parallel pairs
02

Dictionaries & Hash Maps

Think of a dict as a coat-check room: give a name (key), get the coat (value) back instantly — O(1) average lookup. The single most important data structure for frequency counting, memoization, and graph adjacency.

Core Operations

dict.py
# Creation
d = {}
d = dict()
d = {"a": 1, "b": 2}
d = dict(pairs)              # from [(k, v), ...]
d = {k: v for k, v in pairs} # comprehension

# Access
d[key]                      # KeyError if missing!
d.get(key)                  # None if missing
d.get(key, default)         # default if missing

# Mutate
d[key] = value              # set/overwrite
del d[key]                  # remove (KeyError if missing)
d.pop(key)                  # remove + return value
d.pop(key, default)         # safe remove
d.update(other_dict)        # merge in-place
d.setdefault(key, default)  # set if missing, return value

# Iteration
for k in d:                 # keys (default)
for k in d.keys():
for v in d.values():
for k, v in d.items():      # both — most common

# Membership
key in d                    # O(1) average
Pattern Counting frequencies: d[key] = d.get(key, 0) + 1 — but Counter (below) is cleaner.

defaultdict — Auto-init Missing Keys

defaultdict.py
from collections import defaultdict

# Each access to a missing key auto-creates the default
counts = defaultdict(int)        # default 0
counts["apple"] += 1             # no KeyError

groups = defaultdict(list)       # default []
groups["fruit"].append("apple")  # no KeyError

graph = defaultdict(set)
graph[0].add(1)                  # adjacency set

unique_words = defaultdict(set)

# Common: build adjacency list
graph = defaultdict(list)
for u, v in edges:
    graph[u].append(v)
    graph[v].append(u)           # undirected

Counter — Frequency Map on Steroids

counter.py
from collections import Counter

c = Counter("abracadabra")       # {'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}
c = Counter([1, 2, 2, 3, 3, 3])

c["a"]                           # 5 (0 if missing, not KeyError)
c.most_common(k)                 # top-k as [(elem, count), ...]
c.most_common()                  # sorted by freq desc
c.most_common()[:-k-1:-1]        # bottom-k (least common)

# Set-like operations
c1 + c2                          # union (sum counts)
c1 - c2                          # difference (keep positive)
c1 & c2                          # intersection (min counts)
c1 | c2                          # union (max counts)

# Update / subtract
c.update(other_counter_or_iterable)
c.subtract(other)
Analogy Counter is like a tally sheet — it counts each item you feed it, then tells you which items were most popular.

OrderedDict & Python 3.7+ Guarantee

Since Python 3.7, regular dict preserves insertion order. Use OrderedDict only when you need its extra methods (move_to_end, popitem(last=False)) — perfect for LRU cache implementations.

lru_pattern.py
from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity):
        self.cap = capacity
        self.cache = OrderedDict()

    def get(self, key):
        if key not in self.cache:
            return -1
        self.cache.move_to_end(key)   # mark as recently used
        return self.cache[key]

    def put(self, key, value):
        if key in self.cache:
            self.cache.move_to_end(key)
        self.cache[key] = value
        if len(self.cache) > self.cap:
            self.cache.popitem(last=False)  # evict oldest (LRU)
03

Sets & Tuples

Sets give O(1) "have I seen this?" lookups — the backbone of deduplication and graph visited-tracking. Tuples are immutable, hashable lists — perfect for dict keys and multi-return values.

Sets

Set operations
  A = {1, 2, 3, 4}     B = {3, 4, 5, 6}

  A | B   Union        {1, 2, 3, 4, 5, 6}    "in either"
  A & B   Intersection {3, 4}                "in both"
  A - B   Difference   {1, 2}                "in A, not B"
  A ^ B   Symmetric    {1, 2, 5, 6}          "in exactly one"
sets.py
# Creation
s = set()
s = {1, 2, 3}
s = set([1, 2, 2, 3])           # {1, 2, 3} — deduplication!

# Mutation — all O(1) average
s.add(x)
s.remove(x)                     # KeyError if missing
s.discard(x)                    # no error if missing (safer)
s.pop()                         # remove arbitrary element
s.clear()
s.update(iterable)              # add many

# Membership — O(1) average, vs O(n) for lists
x in s

# Set algebra (returns new sets)
s | t   s.union(t, ...)         # |
s & t   s.intersection(t, ...)  # &
s - t   s.difference(t, ...)    # -
s ^ t   s.symmetric_difference(t)
s.issubset(t)
s.issuperset(t)
s.isdisjoint(t)                 # no common elements

# Frozen set — immutable, hashable
fs = frozenset([1, 2, 3])      # can be a dict key
Use case Need fast "seen this before?" checks → use a set. Iterate a list checking membership 1000 times? set is ~1000× faster than list for large N.

Tuples

tuples.py
# Creation
t = ()
t = (1, 2, 3)
t = 1, 2, 3                     # parens optional
t = (1,)                        # single element (comma!)

# Unpacking
a, b, c = t
a, *rest = t                    # rest is a list
first, *middle, last = t
a, b = b, a                     # swap (Pythonic!)

# Multiple return
def divmod_pair(a, b):
    return a // b, a % b
q, r = divmod_pair(17, 5)

# As dict keys (lists can't be!)
memo = {(row, col): value for ...}

# NamedTuple — readable, lightweight class
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
p.x, p.y                         # field access
p[0], p[1]                       # also index access
04

Strings

Strings are immutable sequences of characters — every "modification" creates a new string. Slicing works just like lists. The methods here cover ~95% of string-processing problems.

Slicing & Methods

strings.py
# Slicing — identical to lists
s[::-1]                         # reverse
s[i:j], s[i:j:k], s[-k:]

# Split / Join
s.split()                       # split on whitespace
s.split(',')                    # split on delimiter
s.split(',', maxsplit=2)        # limit splits
','.join(list_of_strings)       # join (must be all strings)
''.join(chars)                  # fastest char->string

# Search / Test
s.find(sub)                     # first index, -1 if missing
s.rfind(sub)                    # last index
s.index(sub)                    # like find but raises ValueError
s.count(sub)                    # occurrences
s.startswith(prefix)
s.endswith(suffix)

# Modify (return new string)
s.replace(old, new)
s.replace(old, new, count)      # limit replacements
s.strip()                       # remove leading/trailing whitespace
s.lstrip(), s.rstrip()
s.strip('xy')                   # remove specific chars from ends
s.upper(), s.lower()
s.title(), s.capitalize()
s.swapcase()

# Test character class
s.isalpha(), s.isdigit(), s.isalnum()
s.isupper(), s.islower()
s.isspace()

# Char <-> int
ord('a')                        # 97
chr(97)                         # 'a'
# Lowercase letters: 97..122, uppercase: 65..90
# Convert letter to 0..25 index:
idx = ord(c) - ord('a')

f-strings & Formatting

fstrings.py
f"Hello {name}"
f"{x:.2f}"                      # 2 decimal places: 3.14
f"{x:0>5d}"                     # zero-padded: 00123
f"{x:>10}"                      # right-align width 10
f"{x:<10}"                      # left-align
f"{x:^10}"                      # center
f"{x:,}"                        # thousands sep: 1,234,567
f"{x:.2%}"                      # percentage: 85.50%
f"{x:#x}"                       # hex: 0xff
f"{x:#b}"                       # binary: 0b1010
f"{x:e}"                        # scientific: 1.23e+04
f"{x!r}"                        # repr form
f"{d[k]=}"                      # debug: shows d[k]=value
Gotcha Strings are immutable — s += "x" in a tight loop is O(n²). Use ''.join(list) instead, or a list of chars then join once.
05

Stacks & Queues (deque)

Use list for stacks (push/pop at end = O(1)). For queues (FIFO), deque gives O(1) on both ends — lists are O(n) for pop(0) or insert(0).

Stack vs Queue
  STACK (LIFO)              QUEUE (FIFO)
  ┌───┐                      ┌───┬───┬───┬───┐
  │ C │ ← push/pop           │ A │ B │ C │ D │
  ├───┤                      └─▲─┴───┴───┴─▲─┘
  │ B │                        │           │
  ├───┤                      enqueue     dequeue
  │ A │                      (rear)      (front)
  └───┘

  Use list for stack:        Use deque for queue:
  arr.append(x)  # push     dq.append(x)      # enqueue
  arr.pop()       # pop      dq.popleft()      # dequeue

deque — Double-Ended Queue

deque.py
from collections import deque

dq = deque()
dq = deque([1, 2, 3])
dq = deque(maxlen=5)            # auto-evicts oldest when full

# All O(1) at both ends
dq.append(x)                   # add right
dq.appendleft(x)               # add left
dq.pop()                       # remove right
dq.popleft()                   # remove left

# Peek (no removal)
dq[0]                          # leftmost
dq[-1]                         # rightmost

# Bulk
dq.extend(iter)                # add many to right
dq.extendleft(iter)            # add many to left (reversed!)

# Rotation (in-place)
dq.rotate(k)                   # rotate right by k
dq.rotate(-k)                 # rotate left by k

len(dq)
x in dq                        # O(n)
Pattern BFS uses deque.popleft() as the queue. Monotonic-stack problems use list with append/pop.
06

Heaps & Priority Queues

Python's heapq is a min-heap. Think of it as a pile where the smallest item is always on top — push anything, pop the minimum in O(log n). For max-heap, negate values.

Min-heap structure
        1                    Heap property:
       / \                   parent ≤ children
      3   2                  (min-heap)
     / \
    7   4

  Stored as array: [1, 3, 2, 7, 4]
       index:        0  1  2  3  4

  Parent of i:        (i - 1) // 2
  Left child of i:    2 * i + 1
  Right child of i:   2 * i + 2

Core Operations

heapq.py
import heapq

heap = []                      # always start with empty list

# All O(log n)
heapq.heappush(heap, x)        # push
heapq.heappop(heap)            # pop smallest (raises if empty)
heapq.heappushpop(heap, x)     # push then pop smallest (atomic)
heapq.heapreplace(heap, x)     # pop then push (atomic)

# Peek (O(1))
heap[0]                        # smallest element

# Heapify existing list — O(n) (not O(n log n)!)
arr = [5, 3, 8, 1, 9]
heapq.heapify(arr)             # arr is now a heap in-place

# Top-K patterns (use heap, not full sort, for streaming)
heapq.nlargest(k, arr)         # [9, 8, 5] for k=3
heapq.nsmallest(k, arr)        # [1, 3, 5]
heapq.nsmallest(k, arr, key=fn)

Max-Heap Trick & Priority Tuples

heap_tricks.py
# --- Max-heap via negation ---
maxheap = []
heapq.heappush(maxheap, -x)
largest = -heapq.heappop(maxheap)

# --- Heap of tuples: sorted by first element, then second, ... ---
# Use for priority queues: (priority, tiebreaker, item)
pq = []
heapq.heappush(pq, (priority, count, item))  # count breaks ties
# WARNING: if item is unorderable (dict, custom obj), tuples
#          comparison will throw TypeError. Always include a
#          tiebreaker counter before the item.

# --- K largest in stream (heap stays size k) ---
import heapq
def k_largest(nums, k):
    heap = nums[:k]
    heapq.heapify(heap)
    for x in nums[k:]:
        if x > heap[0]:
            heapq.heapreplace(heap, x)
    return sorted(heap, reverse=True)
When Top-K problems, Dijkstra, merge K sorted lists, scheduling by priority — anywhere you need "give me the next best" repeatedly.
07

Trees, Graphs & Traversals

Most tree/graph problems in Python don't need fancy libraries — just custom classes for nodes, a dict for adjacency, and recursion (or stack/queue) for traversal.

Node Class Definitions

nodes.py
# --- Singly Linked List ---
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

# Build: 1 -> 2 -> 3
head = ListNode(1, ListNode(2, ListNode(3)))

# --- Binary Tree ---
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

# --- N-ary Tree ---
class Node:
    def __init__(self, val=None, children=None):
        self.val = val
        self.children = children or []

# --- Graph (adjacency list — most common) ---
graph = {0: [1, 2], 1: [0, 3], 2: [0], 3: [1]}
# Or using defaultdict for incremental building
from collections import defaultdict
graph = defaultdict(list)
for u, v in edges:
    graph[u].append(v)
    graph[v].append(u)        # add only if undirected

# Weighted graph
graph = defaultdict(dict)
graph[u][v] = weight

Tree Traversals

Tree traversal order
          1
         / \
        2   3
       / \   \
      4   5   6

  Pre-order  (root, L, R):  1 2 4 5 3 6   "visit on the way down"
  In-order   (L, root, R):  4 2 5 1 3 6   "sorted for BST"
  Post-order (L, R, root):  4 5 2 6 3 1   "visit on the way up"
  Level-order (BFS):        1 2 3 4 5 6   "by depth"
tree_traversal.py
# Recursive (DFS) — clean & Pythonic
def preorder(root):
    if not root: return []
    return [root.val] + preorder(root.left) + preorder(root.right)

def inorder(root):
    if not root: return []
    return inorder(root.left) + [root.val] + inorder(root.right)

def postorder(root):
    if not root: return []
    return postorder(root.left) + postorder(root.right) + [root.val]

# In-order iterative (the classic)
def inorder_iter(root):
    res, stack = [], []
    node = root
    while node or stack:
        while node:               # go all the way left
            stack.append(node)
            node = node.left
        node = stack.pop()        # visit
        res.append(node.val)
        node = node.right
    return res

# Level-order (BFS) — queue + per-level grouping
from collections import deque
def levelorder(root):
    if not root: return []
    res = []
    q = deque([root])
    while q:
        level = []
        for _ in range(len(q)):   # snapshot size = current level width
            node = q.popleft()
            level.append(node.val)
            if node.left:  q.append(node.left)
            if node.right: q.append(node.right)
        res.append(level)
    return res

# Iterative DFS with explicit stack
def preorder_iter(root):
    if not root: return []
    res, stack = [], [root]
    while stack:
        node = stack.pop()
        res.append(node.val)
        if node.right: stack.append(node.right)  # push right first!
        if node.left:  stack.append(node.left)
    return res

Graph BFS & DFS

graph_traversal.py
from collections import deque, defaultdict

# BFS — shortest path in unweighted graph
def bfs(graph, start):
    visited = {start}
    q = deque([start])
    while q:
        node = q.popleft()
        for nb in graph[node]:
            if nb not in visited:
                visited.add(nb)
                q.append(nb)

# BFS with distance tracking
def bfs_dist(graph, start):
    dist = {start: 0}
    q = deque([start])
    while q:
        node = q.popleft()
        for nb in graph[node]:
            if nb not in dist:
                dist[nb] = dist[node] + 1
                q.append(nb)
    return dist

# DFS recursive
def dfs(graph, node, visited=None):
    if visited is None: visited = set()
    visited.add(node)
    for nb in graph[node]:
        if nb not in visited:
            dfs(graph, nb, visited)

# DFS iterative
def dfs_iter(graph, start):
    visited = set()
    stack = [start]
    while stack:
        node = stack.pop()
        if node in visited: continue
        visited.add(node)
        for nb in graph[node]:
            if nb not in visited:
                stack.append(nb)

# Topological sort (Kahn's algorithm, BFS-based)
def topo_sort(num_nodes, edges):
    adj = defaultdict(list)
    indeg = [0] * num_nodes
    for u, v in edges:
        adj[u].append(v)
        indeg[v] += 1
    q = deque([i for i in range(num_nodes) 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) == num_nodes else []  # [] = cycle

# Connected components (undirected)
def count_components(n, edges):
    graph = defaultdict(list)
    for u, v in edges:
        graph[u].append(v); graph[v].append(u)
    seen = set()
    count = 0
    for i in range(n):
        if i not in seen:
            count += 1
            stack = [i]
            while stack:
                u = stack.pop()
                if u in seen: continue
                seen.add(u)
                stack.extend(graph[u])
    return count

Dijkstra — Shortest Path with Weights

dijkstra.py
import heapq

def dijkstra(graph, start, n):
    # graph[u] = [(v, weight), ...]
    dist = [float('inf')] * n
    dist[start] = 0
    pq = [(0, start)]            # (distance, node)
    while pq:
        d, u = heapq.heappop(pq)
        if d > dist[u]: continue # stale entry, skip
        for v, w in graph[u]:
            if dist[u] + w < dist[v]:
                dist[v] = dist[u] + w
                heapq.heappush(pq, (dist[v], v))
    return dist
08

Built-in Essentials

The functions you'll reach for in nearly every problem. Master these and you'll write less code that does more.

Iteration Helpers

iter_helpers.py
# enumerate — index + value pairs
for i, val in enumerate(arr):
    ...
for i, val in enumerate(arr, start=1):  # 1-indexed
    ...

# zip — pair up iterables (stops at shortest)
for a, b in zip(list1, list2):
    ...
list(zip([1,2,3], ['a','b','c']))    # [(1,'a'), (2,'b'), (3,'c')]

# zip_longest — pads missing with fillvalue
from itertools import zip_longest
for a, b in zip_longest(A, B, fillvalue=0):
    ...

# Transpose a matrix
list(zip(*matrix))                   # rows become columns

# map / filter (prefer comprehensions, but useful)
list(map(int, "1 2 3".split()))      # [1, 2, 3]
list(map(fn, iterable))
list(filter(lambda x: x > 0, arr))

# reversed — returns iterator
list(reversed(arr))

# range
range(n)                  # 0..n-1
range(a, b)               # a..b-1
range(a, b, step)
range(n, 0, -1)           # n..1 descending

# sorted — returns new list (see Sorting section)

Math Built-ins

builtins_math.py
# Aggregations
sum(arr)                    # total
sum(arr, start=10)          # total + 10
sum(arr[i] for i in range(0, n, 2))  # sum of even indices

min(arr), max(arr)
min(arr, key=fn)            # element minimizing fn
max(arr, key=lambda x: (x[0], -x[1]))   # multi-criteria

# Pair min/max (returns (smaller, larger))
lo, hi = min(a, b), max(a, b)
# Or:
lo, hi = (a, b) if a < b else (b, a)

# Numeric
abs(x)
round(x, ndigits)
pow(a, b)                   # a ** b
pow(a, b, m)                # (a**b) % m  — efficient modular exp!
divmod(a, b)                # returns (a // b, a % b)
q, r = divmod(17, 5)        # (3, 2)

# Booleans
any([False, True, False])   # True (short-circuits)
all([True, True, False])    # False (short-circuits)
any(x > 0 for x in arr)     # works with generators
all(0 <= x < n for x in arr)

# Type conversions
int("42"), int("ff", 16)    # 42, 255
str(42), float("3.14")
bool(0)                     # False (0, "", [], {}, None are falsy)
list("abc"), set([1,1,2])
dict([("a",1),("b",2)])

# Type checks
isinstance(x, int)
isinstance(x, (int, float)) # multiple types

Competitive Input Reading

input.py
# Fast input
import sys
input = sys.stdin.readline

n = int(input())
arr = list(map(int, input().split()))
matrix = [list(map(int, input().split())) for _ in range(n)]
a, b, c = map(int, input().split())

# Multiple test cases
t = int(input())
for _ in range(t):
    n, m = map(int, input().split())

# Increase recursion limit (default 1000)
import sys
sys.setrecursionlimit(10**6)
09

itertools & functools

Lazy iterators for combinatorics and reduction. Generate permutations, combinations, and Cartesian products in one line — no nested loops.

Combinatorics

combinatorics.py
from itertools import (
    permutations, combinations, combinations_with_replacement,
    product, chain, accumulate, groupby, count, cycle, repeat,
    islice, takewhile, dropwhile, pairwise
)

# Permutations — all orderings
list(permutations([1,2,3]))           # 6 tuples of length 3
list(permutations([1,2,3], 2))        # 6 tuples of length 2

# Combinations — order doesn't matter
list(combinations([1,2,3,4], 2))      # [(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)]
list(combinations_with_replacement([1,2,3], 2))

# Cartesian product
list(product([1,2], ['a','b']))        # [(1,'a'),(1,'b'),(2,'a'),(2,'b')]
list(product([1,2,3], repeat=2))      # 3x3 grid of pairs
list(product(range(n), range(m)))     # all (i,j) coords

# Chain — flatten one level
list(chain([1,2], [3,4], [5]))        # [1,2,3,4,5]
list(chain.from_iterable([[1,2],[3,4]]))  # same, takes iterable of iterables

# Accumulate — prefix sums (and more)
list(accumulate([1,2,3,4]))            # [1, 3, 6, 10]
list(accumulate([1,2,3,4], max))       # running max: [1,2,3,4]
list(accumulate([1,2,3,4], lambda a,b: a*b))  # running product

# Pairwise (Python 3.10+) — sliding pairs
list(pairwise([1,2,3,4]))             # [(1,2),(2,3),(3,4)]

# Infinite iterators (use with islice!)
islice(count(), 5)                    # [0,1,2,3,4]
islice(cycle([1,2,3]), 7)             # [1,2,3,1,2,3,1]
list(repeat(0, 5))                    # [0,0,0,0,0]

# Takewhile / dropwhile
list(takewhile(lambda x: x < 5, [1,4,6,3,8]))  # [1,4]
list(dropwhile(lambda x: x < 5, [1,4,6,3,8]))  # [6,3,8]

# Groupby — must sort first usually!
data = [('a',1),('a',2),('b',3)]
for key, group in groupby(data, key=lambda x: x[0]):
    print(key, list(group))

functools

functools.py
from functools import lru_cache, reduce, cmp_to_key, partial

# lru_cache — automatic memoization (top-down DP!)
@lru_cache(maxsize=None)
def fib(n):
    if n < 2: return n
    return fib(n-1) + fib(n-2)
# Note: args must be hashable. Clear with fib.cache_clear()

# reduce — fold left
reduce(lambda a, b: a + b, [1,2,3,4])   # 10
reduce(lambda a, b: a * b, [1,2,3,4])   # 24
reduce(lambda a, b: a if a > b else b, arr)  # = max(arr)

# cmp_to_key — convert old-style comparator to key func
def compare(a, b):
    if a < b: return -1
    if a > b: return 1
    return 0
arr.sort(key=cmp_to_key(compare))

# partial — bind some arguments
add_5 = partial(lambda a, b: a + b, 5)
add_5(3)  # 8
Pattern @lru_cache(maxsize=None) turns any pure recursive function into memoized DP — fastest way to write top-down DP in a contest.
10

Math & Numbers

Number theory essentials — GCD, modular arithmetic, primes, large integers. Python handles big ints natively (no overflow!), so most of these "just work".

math Module

math_basics.py
import math

# Constants
math.inf              # +infinity
-math.inf             # -infinity
math.pi, math.e
float('inf'), float('-inf')

# Integer math
math.gcd(a, b)        # greatest common divisor
math.lcm(a, b)        # Python 3.9+
math.isqrt(n)         # integer sqrt (floor) — exact, no float
math.comb(n, k)       # n choose k (binomial)
math.perm(n, k)       # n! / (n-k)!
math.factorial(n)

# Float math
math.ceil(x), math.floor(x)
math.log(x), math.log2(x), math.log10(x)
math.exp(x)
math.pow(x, y)        # always float, prefer x ** y for ints
math.sqrt(x)
math.isclose(a, b, rel_tol=1e-9)

# Trig
math.sin, math.cos, math.tan, math.atan, math.atan2

# Useful builtins
divmod(a, b)          # (a // b, a % b) — both at once
pow(a, b, m)          # (a ** b) % m — fast modular exponentiation
bin(13)               # '0b1101'
oct(8)                # '0o10'
hex(255)              # '0xff'
int('1101', 2)        # 13 — parse binary
int('ff', 16)         # 255 — parse hex

Modular Arithmetic & Primes

number_theory.py
MOD = 10**9 + 7       # typical modulo

# Modular addition / multiplication
(a + b) % MOD
(a * b) % MOD
# Modular inverse (Fermat's little theorem, MOD prime)
pow(a, MOD - 2, MOD)   # a^(-1) mod MOD

# Sieve of Eratosthenes
def sieve(n):
    is_prime = [True] * (n + 1)
    is_prime[0] = is_prime[1] = False
    for i in range(2, int(n**0.5) + 1):
        if is_prime[i]:
            for j in range(i*i, n+1, i):
                is_prime[j] = False
    return is_prime        # or [i for i, p in enumerate(is_prime) if p]

# Prime factorization
def factorize(n):
    factors = {}
    d = 2
    while d * d <= n:
        while n % d == 0:
            factors[d] = factors.get(d, 0) + 1
            n //= d
        d += 1
    if n > 1:
        factors[n] = factors.get(n, 0) + 1
    return factors

# Fast exponentiation (when you can't use pow's 3-arg form)
def power(base, exp, mod):
    result = 1
    base %= mod
    while exp > 0:
        if exp & 1:
            result = result * base % mod
        base = base * base % mod
        exp >>= 1
    return result
Python power Python's pow(a, b, m) uses fast modular exponentiation internally — O(log b). Don't reimplement unless asked.
11

Bit Manipulation

Bits are the fastest way to represent small sets, encode state, or pull tricks like "find the unique number". Memorize these operators and a handful of patterns.

Operators & Common Tricks

bits.py
# Operators
a & b      # AND  — both 1 → 1
a | b      # OR   — either 1 → 1
a ^ b      # XOR  — different → 1 (same → 0)
~a         # NOT  — bitwise complement
a << n     # left shift  = a * (2**n)
a >> n     # right shift = a // (2**n)

# ---- Common tricks ----
# Check odd / even
x & 1                      # 1 if odd, 0 if even

# Check i-th bit (0-indexed)
(x >> i) & 1
x & (1 << i)               # nonzero if set

# Set i-th bit
x | (1 << i)

# Clear i-th bit
x & ~(1 << i)

# Toggle i-th bit
x ^ (1 << i)

# Lowest set bit (power of 2)
x & -x                     # e.g. 12 & -12 = 4

# Clear lowest set bit
x & (x - 1)                # 12 & 11 = 8

# Count set bits (popcount)
bin(x).count('1')          # any Python
x.bit_count()              # Python 3.10+ — fast

# Check power of two
x > 0 and (x & (x - 1)) == 0

# Swap two variables (no temp — though Python's a,b=b,a is cleaner)
a ^= b; b ^= a; a ^= b

# Iterate set bits
while x:
    lsb = x & -x
    # use lsb...
    x &= x - 1             # clear lowest set bit

# Iterate all subsets of mask (proper subsets)
sub = mask
while sub:
    # use sub
    sub = (sub - 1) & mask

# Iterate all subsets of size k
from itertools import combinations
for combo in combinations(range(n), k):
    mask = sum(1 << i for i in combo)
Analogy XOR is "difference" — same bits give 0, different bits give 1. That's why a ^ a == 0 and a ^ 0 == a, making it perfect for "find the lone unique element".
12

Sorting & Searching

Python's sort is Timsort — stable, O(n log n). Master key= functions and the bisect module and you'll handle 95% of search/sort problems.

Sorting

sorting.py
# In-place vs new
arr.sort()                  # in-place, returns None
new = sorted(arr)           # new list, original unchanged

# Reverse
arr.sort(reverse=True)
sorted(arr, reverse=True)

# Single key
arr.sort(key=len)           # by length
arr.sort(key=abs)           # by absolute value
arr.sort(key=lambda x: x[1])   # by second element

# Multi-key: tuple key
arr.sort(key=lambda x: (x[0], x[1]))           # asc both
arr.sort(key=lambda x: (x[0], -x[1]))          # asc first, desc second
arr.sort(key=lambda x: (-x[0], -x[1]))         # desc both
arr.sort(key=lambda x: (x.age, x.name))        # multiple fields

# Sort by custom logic via cmp_to_key (rare — usually key= is enough)
from functools import cmp_to_key
arr.sort(key=cmp_to_key(lambda a, b: -1 if a < b else 1))

# Stable sort — Python's sort IS stable.
# Equal-key elements keep original relative order.

# Sort strings by length, then alphabetically
words.sort(key=lambda s: (len(s), s))

# Sort indices by their values in another array
indices = list(range(n))
indices.sort(key=lambda i: values[i])

Binary Search with bisect

bisect_left vs bisect_right
  arr = [1, 3, 3, 3, 5, 7]
  target = 3

  bisect_left(arr, 3)  -> 1   # leftmost insertion point
  bisect_right(arr, 3) -> 4  # rightmost insertion point

  Insert 3 here to keep sorted:
       1  3  3  3  5  7
            ^ left         ^ right
bisect.py
import bisect

# Both assume arr is sorted ascending
bisect.bisect_left(arr, x)      # first index where x could be inserted
bisect.bisect_right(arr, x)     # last index where x could be inserted
bisect.bisect(arr, x)          # alias for bisect_right

# Insert while keeping sorted (O(n) due to shift!)
bisect.insort_left(arr, x)
bisect.insort_right(arr, x)

# Common patterns
# 1. Count elements <= x in sorted arr
count = bisect.bisect_right(arr, x)

# 2. Count elements < x
count = bisect.bisect_left(arr, x)

# 3. Find first element >= x
idx = bisect.bisect_left(arr, x)

# 4. Find first element > x
idx = bisect.bisect_right(arr, x)

# 5. Check if x exists
i = bisect.bisect_left(arr, x)
exists = i < len(arr) and arr[i] == x

Hand-rolled Binary Search

binary_search.py
# Classic: find exact target
def binary_search(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

# Lower-bound style: find first True in [F, F, F, T, T, T]
def first_true(predicate, lo, hi):
    while lo < hi:
        mid = (lo + hi) // 2
        if predicate(mid):
            hi = mid
        else:
            lo = mid + 1
    return lo   # first index where predicate is True

# Binary search on answer (e.g., min capacity such that ...)
def can_achieve(val):
    # ... check if val is feasible ...
    return True

lo, hi = 1, max_possible
while lo < hi:
    mid = (lo + hi) // 2
    if can_achieve(mid):
        hi = mid           # mid works, try smaller
    else:
        lo = mid + 1       # mid too small
return lo                   # smallest feasible value
Overflow note In Python, (lo + hi) // 2 never overflows (big ints). In C++/Java you'd write lo + (hi - lo) // 2.
13

Two Pointers & Sliding Window

Two of the most common patterns in array problems. Both turn O(n²) brute force into O(n) by exploiting structure (sorted input, monotonicity).

Two Pointers

two_pointers.py
# Opposite ends — needs sorted input typically
def two_sum_sorted(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo < hi:
        s = arr[lo] + arr[hi]
        if s == target:
            return [lo, hi]
        elif s < target:
            lo += 1
        else:
            hi -= 1
    return []

# Same direction — fast/slow (Floyd's cycle detection)
def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            return True
    return False

# Same direction — partition (e.g., remove duplicates in-place)
def remove_duplicates(nums):
    slow = 0
    for fast in range(1, len(nums)):
        if nums[fast] != nums[slow]:
            slow += 1
            nums[slow] = nums[fast]
    return slow + 1   # new length

Sliding Window

Sliding window concept
  Fixed window (size k):
    [a b c] d e f        sum of window
     a [b c d] e f       add d, remove a
     a b [c d e] f       add e, remove b
     a b c [d e f]       add f, remove c

  Variable window (expand right, shrink left until valid):
    [a b c d e]  →  too big? shrink from left until constraint holds
sliding_window.py
# Fixed-size window: max sum of subarray length k
def max_sum_k(arr, k):
    window_sum = sum(arr[:k])
    result = window_sum
    for i in range(k, len(arr)):
        window_sum += arr[i] - arr[i - k]   # slide right
        result = max(result, window_sum)
    return result

# Variable window: longest subarray with sum <= k
def longest_subarray_sum_le(arr, k):
    left = 0
    curr_sum = 0
    best = 0
    for right in range(len(arr)):
        curr_sum += arr[right]              # expand
        while curr_sum > k:                # shrink until valid
            curr_sum -= arr[left]
            left += 1
        best = max(best, right - left + 1)
    return best

# Variable window with frequency: longest substring with <= k distinct chars
def longest_substring_k_distinct(s, k):
    from collections import defaultdict
    count = defaultdict(int)
    left = 0
    distinct = 0
    best = 0
    for right, ch in enumerate(s):
        if count[ch] == 0:
            distinct += 1
        count[ch] += 1
        while distinct > k:
            count[s[left]] -= 1
            if count[s[left]] == 0:
                distinct -= 1
            left += 1
        best = max(best, right - left + 1)
    return best
14

Backtracking

Recursion + choice + undo. Think of exploring a tree of decisions: at each step you try an option, recurse, then undo it before trying the next. Three classic templates below.

The Universal Template

backtrack_template.py
def backtrack(state, choices):
    if is_goal(state):
        record(state)             # save a copy!
        return
    for choice in choices:
        if is_valid(state, choice):
            make_move(state, choice)       # mutate
            backtrack(state, choices)      # recurse
            undo_move(state, choice)       # ALWAYS undo

Permutations, Subsets, Combinations

backtrack_classic.py
# Permutations — all orderings of nums
def permutations(nums):
    res = []
    def bt(path, used):
        if len(path) == len(nums):
            res.append(path[:])     # COPY the path!
            return
        for i in range(len(nums)):
            if used[i]: continue
            used[i] = True
            path.append(nums[i])
            bt(path, used)
            path.pop()
            used[i] = False
    bt([], [False] * len(nums))
    return res

# Subsets — all 2^n subsets (no duplicates)
def subsets(nums):
    res = []
    def bt(start, path):
        res.append(path[:])         # every prefix is a subset
        for i in range(start, len(nums)):
            path.append(nums[i])
            bt(i + 1, path)         # only move forward
            path.pop()
    bt(0, [])
    return res

# Combinations — choose k from [1, n]
def combinations(n, k):
    res = []
    def bt(start, path):
        if len(path) == k:
            res.append(path[:])
            return
        for i in range(start, n + 1):
            path.append(i)
            bt(i + 1, path)
            path.pop()
    bt(1, [])
    return res

# Subsets with duplicates — sort + skip same value at same level
def subsets_with_dup(nums):
    nums.sort()                    # group duplicates
    res = []
    def bt(start, path):
        res.append(path[:])
        for i in range(start, len(nums)):
            if i > start and nums[i] == nums[i - 1]:
                continue           # skip duplicate at this level
            path.append(nums[i])
            bt(i + 1, path)
            path.pop()
    bt(0, [])
    return res

N-Queens, Sudoku-Style Grid Backtracking

grid_backtrack.py
# Sudoku solver
def solve_sudoku(board):
    def valid(r, c, ch):
        for i in range(9):
            if board[r][i] == ch or board[i][c] == ch: return False
        br, bc = 3*(r//3), 3*(c//3)
        for i in range(br, br+3):
            for j in range(bc, bc+3):
                if board[i][j] == ch: return False
        return True

    def bt():
        for r in range(9):
            for c in range(9):
                if board[r][c] == '.':
                    for d in '123456789':
                        if valid(r, c, d):
                            board[r][c] = d
                            if bt(): return True
                            board[r][c] = '.'
                    return False      # nothing fits — backtrack
        return True                   # all filled
    bt()
Critical Always path[:] (or path.copy()) when recording results — Python lists are mutable, so without copying you'd capture the same list over and over.
15

Dynamic Programming

DP = recursion + memoization. Three styles in Python: top-down with @lru_cache (easiest), bottom-up with a table (most flexible), and space-optimized (when you only need the last row).

Top-down — Memoization

dp_topdown.py
from functools import lru_cache

# Cleanest form — decorator handles memoization
@lru_cache(maxsize=None)
def fib(n):
    if n < 2: return n
    return fib(n - 1) + fib(n - 2)

# With multiple params (must all be hashable!)
@lru_cache(maxsize=None)
def grid_paths(r, c):
    if r == 0 or c == 0: return 1
    return grid_paths(r-1, c) + grid_paths(r, c-1)

# Explicit memo dict (when params aren't all hashable, or you need control)
def knapsack(values, weights, capacity):
    n = len(values)
    memo = {}
    def solve(i, cap):
        if i == n or cap == 0: return 0
        if (i, cap) in memo: return memo[(i, cap)]
        # Skip item i
        best = solve(i + 1, cap)
        # Take item i (if fits)
        if weights[i] <= cap:
            best = max(best, values[i] + solve(i + 1, cap - weights[i]))
        memo[(i, cap)] = best
        return best
    return solve(0, capacity)

# House robber — classic linear DP
@lru_cache(maxsize=None)
def rob(nums, i=0):
    if i >= len(nums): return 0
    return max(nums[i] + rob(nums, i + 2), rob(nums, i + 1))

Bottom-up — Tabulation

dp_bottomup.py
# Fibonacci — 1D
def fib(n):
    if n < 2: return n
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]

# Space-optimized (only need last 2)
def fib_opt(n):
    if n < 2: return n
    prev, curr = 0, 1
    for _ in range(2, n + 1):
        prev, curr = curr, prev + curr
    return curr

# Longest Common Subsequence — 2D
def lcs(s1, s2):
    m, n = len(s1), len(s2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if s1[i-1] == s2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    return dp[m][n]

# Longest Increasing Subsequence — O(n²) easy version
def lis(nums):
    if not nums: return 0
    dp = [1] * len(nums)
    for i in range(1, len(nums)):
        for j in range(i):
            if nums[j] < nums[i]:
                dp[i] = max(dp[i], dp[j] + 1)
    return max(dp)

# 0/1 Knapsack — 2D
def knapsack(values, weights, capacity):
    n = len(values)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        for w in range(capacity + 1):
            dp[i][w] = dp[i-1][w]   # skip
            if weights[i-1] <= w:
                dp[i][w] = max(dp[i][w], values[i-1] + dp[i-1][w - weights[i-1]])
    return dp[n][capacity]

# Unbounded knapsack (items reusable) — change dp[i-1] to dp[i] on take
def unbounded_knapsack(values, weights, capacity):
    dp = [0] * (capacity + 1)
    for w in range(capacity + 1):
        for i in range(len(values)):
            if weights[i] <= w:
                dp[w] = max(dp[w], values[i] + dp[w - weights[i]])
    return dp[capacity]

# Coin change — minimum coins
def coin_change(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    for a in range(1, amount + 1):
        for c in coins:
            if c <= a:
                dp[a] = min(dp[a], 1 + dp[a - c])
    return dp[amount] if dp[amount] != float('inf') else -1
Pattern recognition "Find number of ways" / "min cost" / "is it possible" + small constraints (≤10⁶ states) → think DP. Identify state (i, remaining capacity, position, etc.), then transition.
16

Classic Patterns

Two reusable building blocks: Union-Find for "are these connected?" queries, and Trie for prefix/word problems.

Union-Find (Disjoint Set)

union_find.py
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n          # union by rank
        self.size = [1] * n          # for size tracking
        self.count = n               # number of components

    def find(self, x):
        # Path compression — flatten the tree
        while self.parent[x] != x:
            self.parent[x] = self.parent[self.parent[x]]
            x = self.parent[x]
        return x

    # Recursive version (also path-compressing)
    def find_rec(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find_rec(self.parent[x])
        return self.parent[x]

    def union(self, x, y):
        px, py = self.find(x), self.find(y)
        if px == py: return False     # already same set
        # Union by rank
        if self.rank[px] < self.rank[py]:
            px, py = py, px
        self.parent[py] = px
        if self.rank[px] == self.rank[py]:
            self.rank[px] += 1
        self.size[px] += self.size[py]
        self.count -= 1
        return True

    def connected(self, x, y):
        return self.find(x) == self.find(y)

# Use case: Kruskal's MST
def kruskal(n, edges):
    edges.sort(key=lambda e: e[2])     # by weight
    uf = UnionFind(n)
    mst_weight = 0
    for u, v, w in edges:
        if uf.union(u, v):
            mst_weight += w
    return mst_weight

Trie (Prefix Tree)

trie.py
class TrieNode:
    def __init__(self):
        self.children = {}    # char -> TrieNode
        self.is_end = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        node = self.root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
        node.is_end = True

    def search(self, word):
        node = self._walk(word)
        return node is not None and node.is_end

    def starts_with(self, prefix):
        return self._walk(prefix) is not None

    def _walk(self, s):
        node = self.root
        for c in s:
            if c not in node.children:
                return None
            node = node.children[c]
        return node

# Use case: word search with wildcard '.'
class WordDictionary:
    def __init__(self):
        self.root = TrieNode()

    def addWord(self, word):
        node = self.root
        for c in word:
            node = node.children.setdefault(c, TrieNode())
        node.is_end = True

    def search(self, word):
        def dfs(node, i):
            if i == len(word): return node.is_end
            c = word[i]
            if c == '.':
                return any(dfs(child, i + 1) for child in node.children.values())
            return c in node.children and dfs(node.children[c], i + 1)
        return dfs(self.root, 0)

Monotonic Stack — Next Greater Element

monotonic.py
# Next greater element (to the right)
def next_greater(arr):
    n = len(arr)
    result = [-1] * n
    stack = []                  # indices, values decreasing
    for i in range(n):
        while stack and arr[stack[-1]] < arr[i]:
            result[stack.pop()] = arr[i]
        stack.append(i)
    return result

# Largest rectangle in histogram
def largest_rectangle(heights):
    stack = []
    max_area = 0
    for i, h in enumerate(heights + [0]):   # sentinel
        while stack and heights[stack[-1]] > h:
            height = heights[stack.pop()]
            width = i if not stack else i - stack[-1] - 1
            max_area = max(max_area, height * width)
        stack.append(i)
    return max_area
17

Pitfalls & Gotchas

The bugs that bite silently. Memorize these and you'll save hours of debugging.

gotchas.py
# 1. MUTABLE DEFAULT ARGUMENTS — shared across calls!
def bad(arr=[]):              # WRONG: arr persists between calls
    arr.append(1)
    return arr
bad(); bad()                  # [1, 1] — surprise!
def good(arr=None):           # RIGHT
    if arr is None: arr = []
    arr.append(1)
    return arr

# 2. INTEGER DIVISION — floor, not truncation!
5 / 2                         # 2.5   (true division)
5 // 2                        # 2     (floor)
-5 // 2                       # -3   ← floors toward -inf, NOT -2!
# To get C-style truncation:
int(-5 / 2)                   # -2
# Or:
def trunc_div(a, b):
    q = a // b
    if (a % b != 0) and ((a < 0) != (b < 0)):
        q += 1
    return q

# 3. NEGATIVE MODULO — always non-negative if divisor positive
-5 % 3                        # 1   (Python) — NOT -2 like C/Java!
-5 % -3                       # -2
# Useful: cycle an index
next_idx = (curr + delta) % n # always in [0, n)

# 4. 2D LIST CREATION — shared row bug
bad  = [[0] * cols] * rows    # all rows ARE the same list!
good = [[0] * cols for _ in range(rows)]   # independent rows

# 5. SHALLOW vs DEEP COPY
import copy
a = [[1, 2], [3, 4]]
b = a                         # same reference
b = a[:]                      # shallow: top-level new, inner same
b = a.copy()                  # same as [:]
b = copy.deepcopy(a)          # fully independent

# 6. FLOAT EQUALITY — never use ==
0.1 + 0.2 == 0.3              # False!
abs(a - b) < 1e-9             # correct
# Or use fractions / Decimal for exact arithmetic
from fractions import Fraction
Fraction(1, 10) + Fraction(2, 10) == Fraction(3, 10)   # True

# 7. CHAINED COMPARISONS — Python perk
if 0 <= x <= 100: ...        # works as expected, evaluates once

# 8. SHORT-CIRCUIT in any/all
any(check(x) for x in huge)   # stops at first True
all(check(x) for x in huge)   # stops at first False

# 9. RECURRENCE / LOOP MUTATING DURING ITERATION
for x in arr:
    arr.append(x)             # infinite loop! iterate over a copy:
for x in arr[:]:
    arr.append(x)

# 10. INTEGER VS FLOAT KEYS — but tuple hashing is fine
d[(1, 2)] = "ok"              # tuples are hashable
# d[[1, 2]] = "no"            # TypeError: lists aren't hashable

# 11. RANGE IS LAZY — materialize if you need it twice
list(range(10))               # use this, not range(10) directly
# range objects can be compared for equality though
range(3) == range(3)          # True (Python 3)

# 12. STRING MULTIPLICATION — chaining
"-" * 50                      # 50 dashes
["x"] * 3                     # ['x', 'x', 'x'] (primitives OK)
[[]] * 3                      # [[],[],[]] — SAME inner list! (mutable!)
18

Complexity Cheat Sheet

Quick reference for picking the right structure. Memorize these and you'll know instantly whether your approach will TLE.

Data Structure Operations

OperationListDict / SetDequeHeap
Access by indexO(1)O(1)O(1) peek min
Search by valueO(n)O(1) avgO(n)O(n)
Insert endO(1) amort.O(1)O(1)O(log n)
Insert frontO(n)O(1)
Insert middleO(n)O(n)
Delete endO(1)O(1)O(1)O(log n)
Delete frontO(n)O(1)
Pop min/maxO(n)O(log n)
SortO(n log n)O(n log n)

Algorithm Complexities

AlgorithmTimeSpaceWhen
BFS / DFSO(V + E)O(V)traversal, shortest path (unweighted)
DijkstraO((V+E) log V)O(V)shortest path, non-negative weights
Binary searchO(log n)O(1)sorted input / answer space
Quicksort / MergesortO(n log n)O(n) merge / O(log n) quickgeneral sorting
Heap push/popO(log n)O(n)priority queue, top-K
Union-Find (with path comp.)O(α(n)) ≈ O(1)O(n)connectivity, MST
KMP string matchO(n + m)O(m)substring search
Backtracking (permutations)O(n!)O(n)generate all orderings
Backtracking (subsets)O(2ⁿ)O(n)generate all subsets
DP (states × transition)O(states × trans)O(states)optimal substructure

Operation Count Rule of Thumb

Rule of thumb Most online judges do ~10⁸ simple operations per second. So:
  • n ≤ 20 → O(2ⁿ) or O(n!) is fine
  • n ≤ 100 → O(n³) is fine
  • n ≤ 1000 → O(n²) is fine
  • n ≤ 10⁵ → need O(n log n)
  • n ≤ 10⁶ → need O(n)
  • n ≥ 10⁹ → need O(log n)
No syntax matches your filter. Try another term.