Algorithmics

Arrays

Matrix

Strings

Hashing

Two Sum

Given an array of integers and a target integer, return the indices of the two numbers that add up to the target.

Implementation
def two_sum(array, target):
    seen = {}
    for index, value in enumerate(array):
        remaining = target - value
        if remaining in seen:
            return [index, seen[remaining]]
        seen[value] = index

Count occurrences of all characters

Given a string, return a dictionary mapping each character to the number of times it appears in the string.

Manual counting
def count_characters(s):
    count = {}
    for char in s:
        count[char] = count.get(char, 0) + 1
    return count
With collections.Counter
from collections import Counter

def count_characters(s):
    return Counter(s)

Check if two strings are anagrams

Two strings are anagrams if one is a rearrangement of the other. Return True if and only if both strings have the same length and the same character counts.

Implementation
def are_anagrams(s1, s2):
    return len(s1) == len(s2) and Counter(s1) == Counter(s2)

Sorting and Searching

Binary Search

Given an array of integers sorted in ascending order and an integer target, return the index of the target or -1 if it does not exist.

Manual implementation
def search(array, target):
    left, right = 0, len(array) - 1
    while left <= right:
        middle = left + (right - left) // 2
        if array[middle] < target:
            left = middle + 1
        elif array[middle] > target:
            right = middle - 1
        else:
            return middle
    return -1
With the bisect module
from bisect import bisect_left

index = bisect_left(array, target)

Recursion and Backtracking

Dynamic Programming

This section covers both bottom-up dynamic programming and top-down memoized recursion. The two are dual formulations of the same idea: a problem is decomposed into overlapping subproblems whose solutions are reused rather than recomputed. Bottom-up DP fills a table iteratively from the base cases up, while memoized recursion expresses the same recurrence directly and caches results as they are computed.

Count paths in a grid

Count all possible paths from the bottom-left to the top-right of an n × n grid, moving only right or up.

Bottom-up dynamic programming
def count_paths(n):
    dp = [[0 if i and j else 1 for i in range(n)] for j in range(n)]
    for i in range(1, n):
        for j in range(1, n):
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
    return dp[n - 1][n - 1]
Top-down recursion with memoization
from functools import cache

def count_paths(n):
    @cache
    def rec(i, j):
        return 1 if not i or not j else rec(i - 1, j) + rec(i, j - 1)

    return rec(n - 1, n - 1)
Closed-form combinatorial solution

Any such path consists of n - 1 right moves and n - 1 up moves, so the count is the number of ways to choose which of the 2n - 2 steps are right moves, i.e. the binomial coefficient C(2n - 2, n - 1).

from math import comb

def count_paths(n):
    return comb(2 * n - 2, n - 1)

Greedy Algorithms

Trees

Definitions

Traversals

Depth

Graphs

A graph is represented as a dictionary of nodes, where each node maps to its list of neighbors.

Traversals

Shortest Path

Math

Basic Arithmetic

Bit Manipulation

Exercises