bigonotes
DSA: Python & HacksHacks & Cheat Sheets

Which Technique When?

A decision guide that maps signals in a problem statement and its constraints to the technique you should reach for first.

8 min readLast updated Hacks & Cheat Sheets

Most interview problems are variations on a few dozen techniques. The hard part is recognising which one applies. This guide is a lookup table: find the signal in the problem statement or constraints, and it tells you what to try first and the one key move that makes it work. It is not a full course on each pattern; for that, work through the NeetCode roadmap.

Start with the constraints#

Input size usually tells you the target complexity before you've read the rest of the problem. These are rough rules of thumb for a typical time limit.

Input size n Target complexity Typical techniques
up to ~12 O(n!)O(n!) permutations, brute force
up to ~20 O(2n)O(2^n) subsets, bitmask DP, backtracking
up to ~500 O(n3)O(n^3) triple loops, interval DP
up to ~5,000 O(n2)O(n^2) nested loops, 2D DP
up to ~10^6 O(nlog⁡n)O(n \log n) or O(n)O(n) sorting, heaps, binary search, two pointers, hashing
beyond that, or a "value" up to 10^9 O(log⁡n)O(\log n) or O(1)O(1) binary search on the answer, math

Arrays and strings#

Most array and string problems fall into prefix sums, sliding windows, two pointers or hashing.

Signal in the problem Reach for Key move
Sum of a range, queried many times Prefix sum pre[j] - pre[i] gives the sum of i..j-1
Count subarrays with sum (or product, xor) = k Prefix sum + hash map Count how often cur - k was seen before; seed the map with {0: 1}
Subarray divisible by k Prefix sum + remainder map Two prefixes with the same % k bound a valid subarray
Condition on odds/evens/thresholds Transform to 0/1 (or ±1), then prefix + map Turns "count of X" into a sum problem
Max/min subarray sum, step by step Kadane's algorithm cur = max(x, cur + x); track the global best
Max subarray in a circular array Kadane twice max(best, total - min_subarray); if all values are negative, return best
Many range updates +k on [l, r], query at the end Difference array diff[l] += k, diff[r + 1] -= k, then prefix-sum
Events over time, overlaps Line sweep Sort (time, +1/-1) events and keep a running count
Values bounded in 0..n Counting sort / index as hash Use the value as an index, or bucket by value
Group by frequency Bucket sort buckets[freq] list, read from high to low
Circular array Modulo index or doubled array i % n, or iterate 2n times
Contiguous subarray/substring with a condition Sliding window Expand right; shrink left while the window is invalid
"Exactly k" distinct / odd / ... in a window Two at-most windows atMost(k) - atMost(k - 1)
Take from left or right end each turn Window on the complement The removed ends leave a contiguous middle window
Sorted array + pair/target Two pointers from both ends Move the pointer that makes the sum closer
In-place removal or compaction Read/write pointers Write pointer only advances on kept items
Two sorted arrays to merge or compare One pointer per array Advance the smaller side
Pair small with large to optimise Sort + two-ends greedy Prove it with an exchange argument
Seen-before / duplicate / complement Hash set or hash map Store what you need before you need it

Sliding window needs the condition to be monotonic: making the window bigger never fixes a violated condition (and shrinking never breaks a satisfied one). Negative numbers usually break this for sum conditions, which is the cue to switch to prefix sums.

Stacks handle nesting and "next greater" questions; binary search handles anything sorted or monotonic.

Signal Reach for Key move
Matching brackets, nested structure, undo Stack Push openers, pop on closers
Next/previous greater or smaller element Monotonic stack Pop while the top breaks the order; the popped item found its answer
Largest rectangle, daily temperatures, stock span Monotonic stack Each index is pushed and popped once: O(n)O(n)
Sorted input, find a value or boundary Binary search With while lo <= hi, a missing target leaves lo at the first larger element and hi at the last smaller one
"Minimum X such that ..." / "maximum X such that ..." Binary search on the answer Write feasible(x); it must be monotonic in x
Rotated sorted array Binary search One half of [lo, mid, hi] is always sorted; check that half first

Intervals#

Interval problems almost always start with a sort. What you sort by depends on the question.

Signal Sort by Why
Merge overlapping intervals, insert an interval Start Process left to right and extend the current merged interval
Count overlaps / meeting rooms Start (or sweep events) A min-heap of end times tracks rooms in use
Max non-overlapping set, min removals to avoid overlap End Picking the earliest finish leaves the most room for the rest

Before coding, clarify whether the boundaries are inclusive: do [1, 2] and [2, 3] overlap?

Linked lists#

Linked-list problems are mostly about pointer bookkeeping, and a few standard tricks remove the edge cases.

Signal Reach for Key move
Head might change (delete, insert, merge) Dummy node dummy.next = head; return dummy.next
Cycle detection, middle node Fast and slow pointers Fast moves 2, slow moves 1 (Floyd)
k-th node from the end Two pointers with a gap Advance one pointer k steps, then move both
Reverse a list or a segment Three pointers prev, cur, nxt; draw it before coding
Needs O(1) access at both ends Doubly linked list (+ hash map for LRU) Sentinel head and tail remove edge cases

Trees#

Most tree problems are a DFS or a BFS. The signal tells you which, and what each call should return.

Signal Reach for Key move
Check every node, path sums, heights DFS (recursive) Decide what each call returns and what the base case is
Shortest/minimum depth, level by level, right side view BFS with a queue Process len(queue) nodes per level
BST validity, k-th smallest In-order traversal In-order visits a BST in sorted order
Lowest common ancestor Post-order DFS Return the node if it matches; if both children return non-null, the current node is the LCA; otherwise pass up the non-null one
Need to move up from a node Parent map One traversal to record parent[child], then BFS outward
Serialise / detect duplicate subtrees Pre-order + null markers Encode each subtree as a string or tuple and hash it

Edge cases to check every time: empty tree, missing left or right child, single leaf.

Tries and heaps#

Tries answer prefix questions; heaps answer "top k" and "smallest next" questions.

Signal Reach for Key move
Prefix search, autocomplete, word dictionary Trie Store an is_end flag (or count, word, suggestions) in each node
Many words sharing prefixes Trie Memory-heavy if words share little; weigh against a hash set
Top k, k-th largest, k closest Heap of size k Min-heap for top k largest: O(nlog⁡k)O(n \log k)
Running median Two heaps Max-heap for lower half, min-heap for upper half
Merge k sorted lists Heap of list heads Push the next item from whichever list you popped
Task scheduling with cooldowns Heap + queue Heap picks the best ready task; queue holds tasks cooling down
Only need max/min after each insert Heap heapify is O(n)O(n); push/pop are O(log⁡n)O(\log n)

Complexity cues for heaps: sort everything is O(nlog⁡n)O(n \log n); scan nn items with a size-k heap is O(nlog⁡k)O(n \log k); heapify then pop kk times is O(n+klog⁡n)O(n + k \log n).

Graphs#

Default to an adjacency list; use a matrix only when the graph is dense or the input already is a grid. For undirected graphs add both u → v and v → u.

The question asks... And the graph is... Use
Shortest path Unweighted BFS
Non-negative weights Dijkstra
Negative weights Bellman-Ford
Connectivity, number of components Static DFS / BFS
Edges arrive over time Union-Find
Order with dependencies Directed acyclic Topological sort (Kahn's BFS)
Cycle? Undirected Union-Find, or DFS tracking the parent
Directed Topological sort (leftover nodes) or DFS with colours
Connect everything at minimum cost Weighted, undirected MST (Kruskal with Union-Find, or Prim)

Very wide graphs make BFS queues large; very deep graphs risk recursion limits in DFS. Use an explicit stack in Python when depth can reach thousands.

Backtracking and DP#

Both explore choices. Backtracking lists every outcome, while DP reuses overlapping subproblems.

Signal Reach for
"All combinations / permutations / subsets", n ≤ 20 Backtracking: choose, recurse, undo
"Count the ways", "minimum cost", "maximum profit", "is it possible" with overlapping choices Dynamic programming
Choices at each step, answer depends only on the current state Top-down memoisation first, then tabulate if needed

A reusable top-down DP shape:

from functools import lru_cache

def min_coins(coins, amount):
    @lru_cache(maxsize=None)
    def solve(remaining):        # state: what's left to make
        if remaining == 0:       # base case: goal reached
            return 0
        if remaining < 0:        # invalid: dead end
            return float("inf")
        # start at inf for min, -inf for max, 0 for counts
        best = float("inf")
        for c in coins:          # explore every choice
            best = min(best, 1 + solve(remaining - c))
        return best

    ans = solve(amount)
    return -1 if ans == float("inf") else ans

print(min_coins((1, 2, 5), 11))   # 3

Checklist before writing DP:

  1. What is the state? (index, remaining amount, last choice, ...)
  2. What does a call return? (count, min cost, max profit, boolean)
  3. Does the answer depend only on the state? If not, add what's missing to the state.
  4. How do sub-results combine? min(cost + sub), max(val + sub), or sum(sub) for counting.

Estimating complexity quickly#

Use these rules of thumb to state your complexity with confidence.

  • Memoised DP: (number of states) × (work per state). Two parameters with ranges x and y and constant choices: O(xy)O(xy). Looping over k choices per state: O(xyk)O(xyk).
  • Backtracking: about (branching factor)^(depth), times the cost of copying each result.
  • String building: each s + t is O(n)O(n); sorting n strings of length L is O(nLlog⁡n)O(n L \log n).
  • Hashing tuples: a tuple key costs O(its length)O(\text{its length}) to hash, so keep keys small.

Key takeaways#

  • Read the constraints first: they usually reveal the target complexity.
  • Contiguous + monotonic condition → sliding window; contiguous + negatives or exact counts → prefix sums with a hash map.
  • Next greater/smaller → monotonic stack; "min X such that" → binary search on the answer.
  • Top k → size-k heap or quickselect; shortest path in an unweighted graph → BFS.
  • For DP, name the state, the return value and the combine step before you write code.

Found a mistake? Tell me on LinkedIn ↗

Further reading#