Interview Prep in 60

Crash course · the coding interview round

DSA in 60 minutes

The eleven topics behind most coding-interview questions, in one hour. Each has key points, a step-by-step animation, a Python template, the questions interviewers actually ask about it, a video, and problems to solve. The Deep dive tab opens with a coding interview playbook (how the 45 minutes are scored and how to spend them), then covers every pattern in depth with graded problem ladders. Press Play or Next on every animation.

Box width = time to spend. After minute 60, use the toolkit: pattern cheat sheet, problem-solving steps, and your first 17 problems.

Minutes 0–5

Big-O: how fast does it grow?

  • Big-O counts how the number of steps grows as the input size n grows. Drop constants: 3n + 5 is O(n).
  • One loop over the input is O(n). A loop inside a loop is O(n²). Halving the search space each step is O(log n). Sorting is O(n log n).
  • Space complexity is the same idea for extra memory (a hash map of n items is O(n) space).
  • The trick most beginners miss: read the constraints first. They tell you which complexity the judge expects.
Graph · operations as n grows
If n is up toAim forTypical approach
10–12O(n!), O(2ⁿ)Try every permutation / backtracking
20–25O(2ⁿ)Try every subset
500O(n³)Three nested loops, some DP
5,000O(n²)Two nested loops, 2D DP
10⁵ – 10⁶O(n log n) or O(n)Sort, hash map, two pointers, sliding window, heap
10⁹ and upO(log n), O(1)Binary search, math

Rule of thumb: a judge runs roughly 10⁸ simple operations per second.

Minutes 5–12

Arrays & hash maps

  • Array / list: items side by side in memory. Reading a[i] is instant. Searching for a value means checking every item.
  • Hash map (dict) / hash set (set): jump straight to a key. Lookup, insert and delete are O(1) on average.
  • The most common speed-up in all of LeetCode: replace an inner search loop with a hash map lookup. That turns O(n²) into O(n).
  • Use it to answer "have I seen this before?", "how many times?", and "where was it?".
OperationArray (list)Hash map / set
get by indexO(1)n/a
find a valueO(n)O(1)
add at endO(1)O(1)
insert / delete in middleO(n)O(1)
keeps order / sortableyesno
Animation · Two Sum with a hash map · target = 12

def twoSum(nums, target):
    seen = {}                      # value -> index
    for i, x in enumerate(nums):
        need = target - x
        if need in seen:           # O(1) lookup instead of a second loop
            return [seen[need], i]
        seen[x] = i

Minutes 12–17

Two pointers

  • Two indexes walk through the array, usually one from each end, or both from the left at different speeds.
  • Clue words: sorted array, pair / triplet that sums to a target, palindrome, reverse in place, remove duplicates.
  • Each step moves one pointer, so the whole thing is O(n) with O(1) extra space.
Animation · pair that sums to 14 in a sorted array

def twoSumSorted(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      # need bigger -> move left pointer right
        else: r -= 1               # need smaller -> move right pointer left

Minutes 17–22

Sliding window

  • A window [L..R] over a contiguous subarray or substring. Grow it by moving R; shrink it by moving L when it breaks a rule.
  • Clue words: longest / shortest / max sum subarray or substring, "at most k", "without repeating".
  • Both pointers only move forward, so it's O(n) instead of checking every subarray in O(n²).
Animation · longest substring without repeating characters

def lengthOfLongestSubstring(s):
    window, l, best = set(), 0, 0
    for r, ch in enumerate(s):
        while ch in window:        # rule broken -> shrink from the left
            window.remove(s[l]); l += 1
        window.add(ch)
        best = max(best, r - l + 1)
    return best

Minutes 22–26

Stack (and queue)

  • Stack = last in, first out, like a pile of plates. In Python: a list with append() and pop().
  • Queue = first in, first out, like a line at a shop. In Python: collections.deque with append() and popleft().
  • Clue words for stack: matching brackets, undo, "next greater element", evaluate an expression, nested structures.
Animation · valid parentheses

def isValid(s):
    pairs = {')': '(', ']': '[', '}': '{'}
    stack = []
    for ch in s:
        if ch in pairs:            # closer: must match the top
            if not stack or stack.pop() != pairs[ch]: return False
        else:
            stack.append(ch)       # opener: push
    return not stack

Minutes 26–31

Binary search

  • On sorted data, check the middle and throw away the half that can't contain the answer. 1,000,000 items need only about 20 checks: O(log n).
  • Clue words: sorted, "find the position", "minimum value that works", n up to 10⁹, required O(log n).
  • Advanced use: binary search on the answer ("smallest speed that finishes in time") when you can check one guess quickly.
Animation · find 72

def search(a, target):
    lo, hi = 0, len(a) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if a[mid] == target: return mid
        if a[mid] < target: lo = mid + 1   # answer is on the right
        else: hi = mid - 1                 # answer is on the left
    return -1

Minutes 31–35

Linked list

  • Each node holds a value and a next pointer. No indexes: to reach item k you walk k steps.
  • Almost every problem is pointer juggling. Draw boxes and arrows on paper before coding.
  • Two standard tricks: a dummy head node to avoid edge cases, and fast/slow pointers (slow moves 1, fast moves 2) to find the middle or detect a cycle.
Animation · reverse a linked list

def reverseList(head):
    prev, curr = None, head
    while curr:
        nxt = curr.next     # 1. remember the rest of the list
        curr.next = prev    # 2. flip the arrow
        prev, curr = curr, nxt  # 3. step forward
    return prev

Minutes 35–43

Trees: DFS and BFS

  • A binary tree node has val, left, right. A BST keeps left < node < right, so searching it is like binary search.
  • DFS (depth-first) goes deep first. Write it with recursion: solve for left child, solve for right child, combine. Most tree problems are 5 lines of DFS.
  • BFS (breadth-first) goes level by level with a queue. Use it for "level order", "minimum depth", "right side view".
  • Recursion recipe: (1) base case if not node: return ... (2) trust the call on children (3) combine.
Animation · traversal order

def maxDepth(root):                       # DFS with recursion
    if not root: return 0
    return 1 + max(maxDepth(root.left), maxDepth(root.right))

from collections import deque
def levelOrder(root):                     # BFS with a queue
    if not root: return []
    q, out = deque([root]), []
    while q:
        level = []
        for _ in range(len(q)):           # everything currently in q = one level
            node = q.popleft(); level.append(node.val)
            if node.left: q.append(node.left)
            if node.right: q.append(node.right)
        out.append(level)
    return out

Minutes 43–49

Graphs and grids

  • A graph is nodes plus edges. Store it as an adjacency list: graph = {node: [neighbours]}.
  • A 2D grid is a graph in disguise: each cell's neighbours are up, down, left, right.
  • Same DFS / BFS as trees, plus one rule: keep a visited set, because graphs can have cycles.
  • BFS finds the shortest path when every edge costs the same. Weighted edges need Dijkstra (learn later).
Animation · number of islands (BFS flood fill)

from collections import deque
def numIslands(grid):
    R, C, seen, count = len(grid), len(grid[0]), set(), 0
    for r in range(R):
        for c in range(C):
            if grid[r][c] == "1" and (r, c) not in seen:
                count += 1
                q = deque([(r, c)]); seen.add((r, c))
                while q:                                  # flood this island
                    y, x = q.popleft()
                    for ny, nx in ((y+1,x),(y-1,x),(y,x+1),(y,x-1)):
                        if 0 <= ny < R and 0 <= nx < C and grid[ny][nx] == "1" and (ny, nx) not in seen:
                            seen.add((ny, nx)); q.append((ny, nx))
    return count

Minutes 49–54

Backtracking

  • Build a solution one choice at a time. After exploring a choice, undo it and try the next one.
  • Clue words: "all combinations", "all permutations", "all subsets", "generate every…", small n (≤ 20).
  • Template: choose → explore (recurse) → un-choose. Cost is exponential, which is fine because n is tiny.
Animation · all subsets of [1, 2, 3]

def subsets(nums):
    res, path = [], []
    def bt(start):
        res.append(path[:])              # every path is a valid subset
        for j in range(start, len(nums)):
            path.append(nums[j])         # choose
            bt(j + 1)                    # explore
            path.pop()                   # un-choose (backtrack)
    bt(0)
    return res

Minutes 54–60

Dynamic programming

  • DP = recursion that remembers answers to sub-problems so each one is solved once.
  • Clue words: "number of ways", "minimum cost", "maximum profit", "can you reach", where each choice depends on smaller versions of the same problem.
  • Recipe: (1) define dp[i] in words (2) write how dp[i] uses smaller i (3) set base cases (4) fill the table in order.
  • Quick start: write the plain recursion, then add @cache from functools. That's top-down DP.
Animation · climbing stairs, 1 or 2 steps at a time, n = 8

def climbStairs(n):                  # bottom-up table
    dp = [1, 1] + [0] * (n - 1)
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]    # arrived by a 1-step or a 2-step
    return dp[n]

from functools import cache
@cache                               # same thing, top-down
def ways(i):
    return 1 if i <= 1 else ways(i-1) + ways(i-2)

After the hour

Toolkit for every problem

Which pattern? Follow the arrows

flowchart TD
  A[Read problem + constraints] --> B{Input sorted?}
  B -- yes --> C[Binary search or two pointers]
  B -- no --> D{Contiguous subarray / substring?}
  D -- yes --> E[Sliding window or prefix sum]
  D -- no --> F{Tree or graph or grid?}
  F -- yes --> G{Shortest path or by levels?}
  G -- yes --> H[BFS with a queue]
  G -- no --> I[DFS / recursion]
  F -- no --> J{Generate ALL combinations?}
  J -- yes --> K[Backtracking]
  J -- no --> L{Count ways or best value from choices?}
  L -- yes --> M[Dynamic programming]
  L -- no --> N{Top k / k-th largest?}
  N -- yes --> O[Heap]
  N -- no --> P[Hash map, sorting, or stack]

Clue → pattern cheat sheet

"seen before", count, duplicates

Hash map / set

sorted + pair / triplet

Two pointers

longest / shortest substring or subarray

Sliding window

sum of a range, many queries

Prefix sums: pre[j] - pre[i]

brackets, "next greater", undo

Stack (monotonic stack for "next greater")

sorted, or "minimum value that works"

Binary search (on index or on answer)

top k, k-th largest, merge k lists

Heap: heapq (it's a min-heap)

intervals, meetings, overlaps

Sort by start, then sweep

tree depth, paths, subtrees

DFS recursion

shortest steps, grid, levels

BFS + visited set

all subsets / permutations

Backtracking

number of ways, min cost, max profit

Dynamic programming

How to attack a problem (in order)

  1. Read the constraints. Use the Big-O table to know the target complexity.
  2. Solve the examples by hand on paper. Notice what your brain does.
  3. Say the brute force out loud, even if it's slow. Note its Big-O.
  4. Find the bottleneck and match it to a pattern: a repeated search becomes a hash map; a nested loop on sorted data becomes two pointers.
  5. Code from the template above, then rename variables to fit.
  6. Test edge cases: empty input, one element, all duplicates, negatives, already sorted.
  7. Stuck for 20–25 minutes? Read the solution, understand it, close it, and re-code it from memory the next day.

Python built-ins you'll use daily

from collections import deque, Counter, defaultdict
import heapq, bisect

q = deque(); q.append(x); q.popleft()          # queue, O(1) both ends
cnt = Counter("banana")                         # {'a': 3, 'n': 2, 'b': 1}
g = defaultdict(list); g[u].append(v)           # adjacency list
h = []; heapq.heappush(h, x); heapq.heappop(h)  # min-heap; push -x for max-heap
i = bisect.bisect_left(sorted_list, x)          # binary search built in
nums.sort(key=lambda p: p[0])                   # sort by first item, O(n log n)
float('inf')                                    # starting value for "minimum so far"

Your first 17 problems, in order

#ProblemPattern

Keep watching and visualizing