Skip to content
Laviyx Learn

Try

Data Structures and Algorithms Cheat Sheet

Big O, Python structure costs, standard library tools, and templates for binary search, two pointers, BFS, DFS, Dijkstra and dynamic programming.

Updated

# → value shows what an expression returns. # name → value shows what a variable holds after that line.

Big O at a glance#

Big ONameExample
O(1)constantindex a list, dict lookup
O(log n)logarithmicbinary search, heap push/pop
O(n)linearone pass over the data
O(n log n)linearithmicsorted(), merge sort
O(n²)quadraticall pairs, nested loops
O(2ⁿ)exponentialall subsets
O(n!)factorialall orderings

Sequential steps add; nested loops multiply; keep the biggest term and drop constants.

Python structure costs#

Operationlistdequedict / setheapq
access by indexO(1)O(n) middlen/aO(1) for h[0]
append / addO(1)*O(1) both endsO(1) avgO(log n) push
pop endO(1)O(1) both endsn/aO(log n) pop
insert/pop frontO(n)O(1)n/an/a
x inO(n)O(n)O(1) avgO(n)
sortO(n log n)n/an/an/a

* amortized

Handy standard library tools#

Python
from collections import Counter, defaultdict, deque
import heapq
from bisect import bisect_left, bisect_right

Counter("banana").most_common(1)       # → [('a', 3)]

groups = defaultdict(list)
groups["even"].append(2)
dict(groups)                           # → {'even': [2]}

q = deque([1, 2, 3])
q.appendleft(0)
q.popleft()                            # → 0
q.pop()                                # → 3

h = [5, 1, 4]
heapq.heapify(h)
heapq.heappop(h)                       # → 1
heapq.nlargest(2, [7, 3, 9, 1])        # → [9, 7]

data = [1, 2, 2, 2, 5]
bisect_left(data, 2)                   # → 1
bisect_right(data, 2)                  # → 4
Python
def binary_search(items, target):
    low, high = 0, len(items) - 1
    while low <= high:
        mid = (low + high) // 2
        if items[mid] == target:
            return mid
        if items[mid] < target:
            low = mid + 1
        else:
            high = mid - 1
    return -1

binary_search([3, 8, 15, 23, 42], 23)  # → 3

Two pointers and sliding window#

Python
def pair_sum(sorted_nums, target):
    l, r = 0, len(sorted_nums) - 1
    while l < r:
        s = sorted_nums[l] + sorted_nums[r]
        if s == target:
            return l, r
        if s < target:
            l += 1
        else:
            r -= 1

pair_sum([1, 3, 4, 6, 8], 10)          # → (2, 3)

def longest_unique(s):
    seen, left, best = set(), 0, 0
    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)
    return best

longest_unique("abcabcbb")             # → 3

Prefix sums#

Python
nums = [3, 1, 4, 1, 5]
prefix = [0]
for x in nums:
    prefix.append(prefix[-1] + x)
prefix                                 # → [0, 3, 4, 8, 9, 14]
prefix[4] - prefix[1]                  # → 6

sum(nums[i..j]) = prefix[j + 1] - prefix[i].

Linked list node and reversal#

Python
class Node:
    def __init__(self, value, next=None):
        self.value, self.next = value, next

def reverse(head):
    prev = None
    while head:
        head.next, prev, head = prev, head, head.next
    return prev

r = reverse(Node(1, Node(2, Node(3))))
[r.value, r.next.value, r.next.next.value]   # → [3, 2, 1]

Trees#

Python
class TreeNode:
    def __init__(self, value, left=None, right=None):
        self.value, self.left, self.right = value, left, right

def inorder(n):
    return inorder(n.left) + [n.value] + inorder(n.right) if n else []

def height(n):
    return -1 if n is None else 1 + max(height(n.left), height(n.right))

root = TreeNode(2, TreeNode(1), TreeNode(3, None, TreeNode(4)))
inorder(root)                          # → [1, 2, 3, 4]
height(root)                           # → 2
Python
graph = {"A": ["B", "C"], "B": ["D"], "C": ["D"], "D": []}

def bfs(start):
    seen, order, queue = {start}, [], deque([start])
    while queue:
        v = queue.popleft()
        order.append(v)
        for n in graph[v]:
            if n not in seen:
                seen.add(n)
                queue.append(n)
    return order

def dfs(v, seen=None):
    seen = seen if seen is not None else []
    seen.append(v)
    for n in graph[v]:
        if n not in seen:
            dfs(n, seen)
    return seen

bfs("A")                               # → ['A', 'B', 'C', 'D']
dfs("A")                               # → ['A', 'B', 'D', 'C']

Dijkstra#

Python
def dijkstra(g, start):
    dist = {v: float("inf") for v in g}
    dist[start] = 0
    heap = [(0, start)]
    while heap:
        d, u = heapq.heappop(heap)
        if d > dist[u]:
            continue
        for v, w in g[u]:
            if d + w < dist[v]:
                dist[v] = d + w
                heapq.heappush(heap, (d + w, v))
    return dist

dijkstra({"A": [("B", 4), ("C", 1)], "C": [("B", 2)], "B": []}, "A")   # → {'A': 0, 'C': 1, 'B': 3}

Dynamic programming#

Python
from functools import cache

@cache
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

fib(50)                                # → 12586269025

def min_coins(coins, amount):
    dp = [0] + [float("inf")] * amount
    for a in range(1, amount + 1):
        for c in coins:
            if c <= a:
                dp[a] = min(dp[a], dp[a - c] + 1)
    return dp[amount]

min_coins([1, 3, 4], 6)                # → 2

Define the state, write the recurrence, set the base cases, choose the order, read the answer.

Which technique?#

ClueTry
seen before / pairs to a targethash map or set
sorted input, pairs, in placetwo pointers
contiguous rangesliding window, prefix sums
"smallest value that works"binary search on the answer
next greater / smallermonotonic stack
top k / kth largestheap
fewest stepsBFS
connected groups, cyclesDFS, union-find
dependenciestopological sort
all combinationsbacktracking
count ways / min cost with repeated subproblemsdynamic programming

Want the full explanations? Go to the Learn Data Structures and Algorithms course →