1 2-D Dynamic Programming Medium Grid DP

Unique Paths

In plain English: Count the number of ways to walk from the top-left to the bottom-right of a grid, only moving right or down.

dp[i][j] = number of ways to reach cell (i,j). Each cell can only come from above or from the left, so dp[i][j] = dp[i-1][j] + dp[i][j-1]. Optimize to a single row since we only look at the previous row.

Prompt

A robot is in the top-left corner of an m x n grid. It can only move right or down. How many unique paths are there to the bottom-right corner?

Solution

def unique_paths(m, n):
    row = [1] * n  # base: top row all 1s
    for i in range(1, m):
        for j in range(1, n):
            row[j] += row[j-1]  # above + left
    return row[-1]

# Test: unique_paths(3, 7) == 28
# Test: unique_paths(3, 2) == 3
O(m*n) time · O(n) space
Full 2-D Dynamic Programming guide →
2 Advanced Graphs Easy DFS Recursive

Flood fill

In plain English: Starting from a pixel, change its color and all connected same-color pixels to a new color (like the paint bucket tool).

DFS from the starting pixel, changing color as you go. Only recurse into neighbors that match the original color — naturally stops at boundaries.

Prompt

Given a 2D image grid, a starting pixel (sr, sc), and a new color, flood fill the connected region of the starting pixel's color with the new color.

Solution

def flood_fill(image, sr, sc, new_color):
    orig = image[sr][sc]
    if orig == new_color:
        return image
    m, n = len(image), len(image[0])

    def dfs(i, j):
        if i < 0 or i >= m or j < 0 or j >= n or image[i][j] != orig:
            return
        image[i][j] = new_color  # fill
        dfs(i+1, j); dfs(i-1, j); dfs(i, j+1); dfs(i, j-1)

    dfs(sr, sc)
    return image

# Test: flood_fill([[1,1,1],[1,1,0],[1,0,1]], 1, 1, 2)
# => [[2,2,2],[2,2,0],[2,0,1]]
O(m*n) time · O(m*n) space
Full Advanced Graphs guide →
3 Advanced Trees Easy Recursive Max

Diameter of binary tree

In plain English: Find the longest path between any two nodes in a binary tree, measured in edges.

At each node, the longest path through it is left_height + right_height. Track the global maximum while computing heights bottom-up.

Prompt

Find the diameter of a binary tree — the length of the longest path between any two nodes (in edges).

Solution

def diameter_of_binary_tree(root):
    diameter = [0]
    def height(node):
        if not node:
            return 0
        lh = height(node.left)
        rh = height(node.right)
        diameter[0] = max(diameter[0], lh + rh)  # path through this node
        return 1 + max(lh, rh)
    height(root)
    return diameter[0]

# Test: tree [1,2,3,4,5] => diameter = 3 (path 4->2->1->3)
O(n) time · O(h) space
Full Advanced Trees guide →
4 Arrays & Strings Easy Two Pointers

Reverse array in-place

In plain English: You're given a list of numbers and need to flip the order without creating a new list.

Two pointers converging from opposite ends is the go-to for symmetric array operations — each swap brings both ends closer to the center.

Prompt

Given an array a, reverse it in-place.

Solution

def reverse(a):
    l, r = 0, len(a) - 1
    # converge from both ends
    while l < r:
        a[l], a[r] = a[r], a[l]
        l += 1
        r -= 1
    return a

# Test: reverse([1,2,3,4,5]) == [5,4,3,2,1]
O(n) time · O(1) space
Full Arrays & Strings guide →
5 Backtracking Medium Backtrack+Prune

Letter combinations of phone

In plain English: Given phone number digits, list all possible letter combinations from the keypad (like T9).

Each digit maps to 3-4 letters. Build combinations by choosing one letter per digit and backtracking — the recursion tree has at most 4^n leaves.

Prompt

Given a string of digits 2-9, return all possible letter combinations that the number could represent (phone keypad mapping).

Solution

def letter_combinations(digits):
    if not digits:
        return []
    phone = {'2':'abc','3':'def','4':'ghi','5':'jkl',
             '6':'mno','7':'pqrs','8':'tuv','9':'wxyz'}
    result = []
    def backtrack(idx, path):
        if idx == len(digits):
            result.append(''.join(path))
            return
        for c in phone[digits[idx]]:
            path.append(c)
            backtrack(idx + 1, path)
            path.pop()  # backtrack
    backtrack(0, [])
    return result

# Test: letter_combinations("23") == ["ad","ae","af","bd","be","bf","cd","ce","cf"]
O(4^n) time · O(n) space
Full Backtracking guide →
7 Bit Manipulation Easy Bit Counting

Count set bits

In plain English: Count how many 1s appear in the binary form of a number.

n & (n-1) clears the lowest set bit each time — loop until n is zero and count the iterations.

Prompt

Count the number of 1-bits (set bits) in the binary representation of a non-negative integer.

Solution

def count_bits(n):
    count = 0
    while n:
        n &= n - 1  # clear lowest set bit
        count += 1
    return count

# Test: count_bits(11) == 3  (1011 in binary)
# Test: count_bits(128) == 1  (10000000)
# Test: count_bits(0) == 0
O(k) time where k = number of set bits · O(1) space
Full Bit Manipulation guide →
8 Dynamic Programming Easy Tabulation

Climbing stairs

In plain English: Count the number of different ways to reach the top of a staircase if you can take 1 or 2 steps at a time.

To reach step n you came from step n-1 or n-2, so ways(n) = ways(n-1) + ways(n-2) — the same recurrence as Fibonacci.

Prompt

You can climb 1 or 2 steps at a time. How many distinct ways can you climb n stairs?

Solution

def climb_stairs(n):
    if n <= 2:
        return n
    a, b = 1, 2
    for _ in range(3, n + 1):
        a, b = b, a + b  # ways[i] = ways[i-1] + ways[i-2]
    return b

# Test: climb_stairs(2) == 2
# Test: climb_stairs(3) == 3
# Test: climb_stairs(5) == 8
O(n) time · O(1) space
Full Dynamic Programming guide →
9 Graphs Easy Stack + Visited

DFS iterative

In plain English: Walk every reachable node in a graph by going as deep as possible before backtracking, using a stack.

Replacing the BFS queue with a stack switches from breadth-first to depth-first — LIFO explores deep before wide.

Prompt

Traverse a graph depth-first using an explicit stack.

Solution

def dfs_iterative(graph, start):
    visited = {start}
    stack = [start]  # stack = LIFO = depth first
    order = []
    while stack:
        node = stack.pop()
        order.append(node)
        for nb in graph[node]:
            if nb not in visited:
                visited.add(nb)
                stack.append(nb)
    return order
O(V + E) time · O(V) space
Full Graphs guide →
10 Greedy Easy Greedy Choice

Best time to buy/sell stock

In plain English: Find the best day to buy and the best day to sell a stock to make the most money.

Track the minimum price seen so far. At each day, the best you can do is sell at today's price minus the cheapest buy — one pass, O(1) space.

Prompt

Given an array of stock prices (one per day), find the maximum profit from one buy and one sell (buy before sell).

Solution

def max_profit(prices):
    min_price = float('inf')
    max_profit = 0
    for p in prices:
        min_price = min(min_price, p)
        max_profit = max(max_profit, p - min_price)  # sell today, bought at min
    return max_profit

# Test: max_profit([7,1,5,3,6,4]) == 5  (buy at 1, sell at 6)
# Test: max_profit([7,6,4,3,1]) == 0  (no profit possible)
O(n) time · O(1) space
Full Greedy guide →
11 Hash Maps Easy Complement Map

Two sum (unsorted)

In plain English: Find two numbers in an unsorted list that add up to a target and return their indices.

Instead of checking all pairs O(n²), store each value in a hashmap — then lookup for the complement is O(1).

Prompt

Given unsorted array a and target t, return indices of the pair that sums to t.

Solution

def two_sum(a, t):
    seen = {}
    for i, x in enumerate(a):
        # check complement before storing
        if t - x in seen:
            return [seen[t - x], i]
        seen[x] = i
    return []

# Test: two_sum([2,7,11,15], 9) == [0, 1]
O(n) time · O(n) space
Full Hash Maps guide →
12 Heaps Easy Heap Operations

Min-heap push and pop

In plain English: Show how to add and remove elements from a min-heap, which always gives you the smallest element first.

A heap is a complete binary tree stored in an array where each parent is smaller than its children — push and pop maintain this property in O(log n).

Prompt

Demonstrate pushing and popping elements from a min-heap using Python's heapq module.

Solution

import heapq

def heap_demo():
    h = []
    for val in [5, 3, 8, 1, 2]:
        heapq.heappush(h, val)  # O(log n) insert
    # h is now a valid min-heap

    result = []
    while h:
        result.append(heapq.heappop(h))  # O(log n) extract-min
    return result

# Test: heap_demo() == [1, 2, 3, 5, 8]
O(n log n) time · O(n) space
Full Heaps guide →
13 Intervals Easy Sort + Check

Meeting Rooms

In plain English: Check if any meetings in your schedule overlap with each other.

Sort by start time, then check each consecutive pair. If any meeting starts before the previous one ends, there's a conflict. Sorting makes the check O(n).

Prompt

Given an array of meeting time intervals [[start, end], ...], determine if a person could attend all meetings (i.e., no two meetings overlap).

Solution

def can_attend_meetings(intervals):
    intervals.sort(key=lambda x: x[0])
    for i in range(1, len(intervals)):
        if intervals[i][0] < intervals[i-1][1]:  # overlap
            return False
    return True

# Test: can_attend_meetings([[0,30],[5,10],[15,20]]) == False
# Test: can_attend_meetings([[7,10],[2,4]]) == True
O(n log n) time · O(1) space
Full Intervals guide →
14 Linked Lists Easy Pointer Walk

Insert at position k

In plain English: Add a new node at a specific position in a linked list.

In a linked list, insertion is O(1) once you're at the right spot — the cost is in walking there.

Prompt

Insert value val at position k (0-indexed) in a linked list.

Solution

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

def insert_at(head, val, k):
    new = Node(val)
    if k == 0:
        new.next = head
        return new
    cur = head
    for _ in range(k - 1):
        cur = cur.next
    # splice new node in
    new.next = cur.next
    cur.next = new
    return head

# Test: 1->2->3, insert 9 at k=1 => 1->9->2->3
O(k) time · O(1) space
Full Linked Lists guide →
15 Math & Geometry Easy Cycle Detection

Happy Number

In plain English: Keep replacing a number with the sum of the squares of its digits. It's happy if you eventually reach 1.

The sequence either converges to 1 or enters a cycle. Use Floyd's cycle detection (slow/fast pointers) or a hash set to detect the cycle. If you hit 1, it's happy.

Prompt

Write an algorithm to determine if a number n is happy. A happy number is defined by repeatedly summing the squares of its digits until you reach 1 or loop endlessly.

Solution

def is_happy(n):
    def digit_sq_sum(x):
        total = 0
        while x:
            x, d = divmod(x, 10)
            total += d * d
        return total

    # Floyd's cycle detection: fast moves 2 steps, slow 1
    slow = n
    fast = digit_sq_sum(n)
    while fast != 1 and slow != fast:
        slow = digit_sq_sum(slow)
        fast = digit_sq_sum(digit_sq_sum(fast))  # two steps so it laps slow if a cycle exists
    return fast == 1

# Test: is_happy(19) == True   (19->82->68->100->1)
# Test: is_happy(2) == False
O(log n) time · O(1) space
Full Math & Geometry guide →
16 Recursion Easy Base + Memo

Fibonacci (memoized)

In plain English: Compute the nth Fibonacci number without redundantly recalculating the same sub-values.

Naive recursion recomputes fib(k) exponentially many times — memoization stores each result once, collapsing O(2^n) to O(n).

Prompt

Compute the nth Fibonacci number efficiently.

Solution

# Recursive + memo
def fib(n, memo={}):
    if n <= 1:
        return n
    if n not in memo:
        memo[n] = fib(n - 1) + fib(n - 2)  # store to avoid recomputation
    return memo[n]

# Iterative (O(1) space)
def fib_iter(n):
    if n <= 1:
        return n
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b

# Test: fib(10) == 55
O(n) time · O(n) space (memo) / O(1) space (iter)
Full Recursion guide →
17 Sliding Window Easy Kadane's Variant

Best Time to Buy and Sell Stock

In plain English: Find the best day to buy and the best later day to sell a stock to maximize your profit.

Track the minimum price seen so far and compute profit at each step. This is Kadane's algorithm in disguise — the max gain ending here is today's price minus the running minimum.

Prompt

Given an array prices where prices[i] is the price of a stock on the ith day, return the maximum profit you can achieve from one buy and one sell. You must buy before you sell.

Solution

def max_profit(prices):
    min_price = float('inf')
    max_profit = 0
    for price in prices:
        min_price = min(min_price, price)        # track cheapest buy
        max_profit = max(max_profit, price - min_price)  # best sell
    return max_profit

# Test: max_profit([7,1,5,3,6,4]) == 5  (buy@1, sell@6)
# Test: max_profit([7,6,4,3,1]) == 0    (no profit possible)
O(n) time · O(1) space
Full Sliding Window guide →
18 Sorting Easy Shift & Place

Insertion sort

In plain English: Sort a list by repeatedly picking the next element and inserting it into its correct spot.

Builds the sorted portion one element at a time by shifting — efficient for nearly-sorted data because few shifts are needed.

Prompt

Sort an array using insertion sort.

Solution

def insertion_sort(a):
    for i in range(1, len(a)):
        key = a[i]
        j = i - 1
        while j >= 0 and a[j] > key:  # shift larger elements right
            a[j + 1] = a[j]
            j -= 1
        a[j + 1] = key
    return a

# Test: insertion_sort([5,3,1,4,2]) == [1,2,3,4,5]
O(n^2) time · O(1) space · Stable
Full Sorting guide →
19 Stacks & Queues Easy Operand Stack

Evaluate postfix expression

In plain English: Calculate the result of a math expression written in postfix notation like '2 1 + 3 *'.

Postfix notation eliminates the need for parentheses and precedence — operands are always ready on the stack when an operator appears.

Prompt

Evaluate a postfix (reverse Polish notation) expression.

Solution

def eval_postfix(tokens):
    stack = []
    ops = {'+': lambda a,b: a+b, '-': lambda a,b: a-b,
           '*': lambda a,b: a*b, '/': lambda a,b: int(a/b)}
    for t in tokens:
        if t in ops:
            b, a = stack.pop(), stack.pop()  # pop order matters: b first, then a
            stack.append(ops[t](a, b))
        else:
            stack.append(int(t))
    return stack[0]

# Test: eval_postfix(["2","1","+","3","*"]) == 9
O(n) time · O(n) space
Full Stacks & Queues guide →
20 Trees Easy Recursive Descent

BST insert

In plain English: Add a new value to a binary search tree so that the BST property is preserved.

BST property guides you left or right at each node — the first None you reach is where the new node belongs.

Prompt

Insert a value into a binary search tree.

Solution

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

def bst_insert(root, val):
    if root is None:  # None = empty slot, insert here
        return TreeNode(val)
    if val < root.val:
        root.left = bst_insert(root.left, val)
    else:
        root.right = bst_insert(root.right, val)
    return root
O(h) time · O(h) space
Full Trees guide →
21 Tries Medium Trie Build

Implement Trie / Prefix Tree

In plain English: Build a tree-like structure where each path from root to a node represents a prefix of inserted words. Support inserting words and checking if a word or prefix exists.

Each node is a dictionary of children keyed by character. Insert walks the tree creating nodes; search walks and checks the end marker. Prefix search is just search without the end check.

Prompt

Implement a trie with insert, search, and startsWith methods.

Solution

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

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

    def insert(self, word):
        # create missing child nodes along the path for each character
        node = self.root
        for ch in word:
            if ch not in node.children:
                node.children[ch] = TrieNode()
            node = node.children[ch]
        node.is_end = True

    def search(self, word):
        node = self._find(word)
        # must land on a node AND it must be a complete word, not just a prefix
        return node is not None and node.is_end

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

    def _find(self, prefix):
        # traverse character-by-character; return None early if path doesn't exist
        node = self.root
        for ch in prefix:
            if ch not in node.children:
                return None
            node = node.children[ch]
        return node

# Test: t = Trie(); t.insert("apple")
# t.search("apple") == True; t.search("app") == False
# t.starts_with("app") == True
O(m) time per operation · O(n*m) space total
Full Tries guide →
22 Two Pointers Easy Slow-Fast Write

Remove Duplicates from Sorted Array

In plain English: Remove duplicate values from a sorted list in-place and return how many unique values remain.

Slow pointer marks the write position, fast pointer scans ahead. When fast finds a new value, write it at slow+1 and advance slow. The sorted order guarantees duplicates are adjacent.

Prompt

Given a sorted array nums, remove the duplicates in-place such that each element appears only once. Return the new length.

Solution

def remove_duplicates(nums):
    if not nums:
        return 0
    slow = 0  # slow = next write position for a unique value
    for fast in range(1, len(nums)):  # fast scans ahead for new values
        if nums[fast] != nums[slow]:
            slow += 1
            nums[slow] = nums[fast]
    return slow + 1

# Test: nums = [1,1,2]; remove_duplicates(nums) == 2; nums[:2] == [1,2]
# Test: nums = [0,0,1,1,1,2,2,3,3,4]
#       remove_duplicates(nums) == 5; nums[:5] == [0,1,2,3,4]
O(n) time · O(1) space
Full Two Pointers guide →