This is the long-form companion to:
Use this document when you want to understand why a pattern works, not just how to type it.
In DSA interviews, most problems are not asking for brand-new algorithms. They ask whether you can map a prompt to a known family:
- Lookup/frequency problems -> hash maps/sets
- Sorted constraints -> binary search or two pointers
- Contiguous window with constraints -> sliding window
- Parentheses / nearest greater -> stacks
- Reachability -> BFS/DFS
- Optimization over choices -> dynamic programming or greedy
A reliable process:
- Identify input structure: array, string, linked list, tree, graph, interval list.
- Identify objective: existence, count, min/max, enumeration, reconstruction.
- Identify constraints: sorted? bounded values? small n? repeated queries?
- Pick the smallest pattern that satisfies correctness + complexity.
Use hashing when you need O(1)-average membership, counts, or complement lookups.
Typical prompts:
- "contains duplicate"
- "find pair that sums to target"
- "group by signature" (anagrams)
- "first unique / frequency"
HashSet: each element appears at most once.HashMap<K, V>: exactly one value per key, usually count/index/state.
Many O(n^2) brute-force loops repeatedly ask membership questions. Hashing memoizes those questions so each check is O(1)-average.
- Counting idiom:
*map.entry(x).or_insert(0) += 1; - For strings, decide key type early (
String,Vec<u8>, or fixed-size signature arrays). - If sums can exceed
i32, promote toi64.
- Time: O(n) average
- Space: O(n)
Use when a monotonic movement in one or both directions can discard impossible states.
Typical prompts:
- sorted pair sum
- in-place dedupe in sorted array
- palindrome checks
- 3Sum after sorting
- Window or pair boundaries only move forward (or inward), never back.
- Every move preserves search completeness (no valid answer is skipped).
On sorted inputs, pointer movement corresponds to predictable changes in value. You can rule out whole ranges in one step.
- Be careful with
usizeunderflow onr -= 1. - Prefer
while l < rand initializer = n - 1only whenn > 0.
- Time: usually O(n) after sorting (if needed, total O(n log n))
- Space: O(1) extra (excluding output)
Use when the answer is about a contiguous range and constraints can be maintained incrementally.
Typical prompts:
- longest substring with condition
- minimum window containing condition
- at most k distinct
- fixed-size maximum sum
- Window
[l, r]represents the current candidate. - Data structure (counts/frequency) exactly reflects elements in current window.
- If invalid, move
luntil valid again.
Each pointer moves at most n times, so even nested while loops remain linear overall.
- Strings: avoid direct indexing by byte unless ASCII-safe. Use
as_bytes()when problem guarantees uppercase/lowercase English letters. - Remove zero counts from maps to keep distinct-size checks accurate.
- Time: O(n)
- Space: O(k) or O(alphabet)
Use when you need nearest greater/smaller elements or when elements are resolved in one direction.
Typical prompts:
- next greater element
- daily temperatures
- largest rectangle in histogram
- Stack maintains monotonic ordering (increasing or decreasing values/indexes).
- Each index is pushed once and popped once.
The stack stores unresolved candidates. A new element resolves all previously waiting elements that it dominates.
- Store indices in stack, not values, to compute distances.
- Pattern:
while let Some(&j) = st.last() { ... }
- Time: O(n)
- Space: O(n)
Use when the search space is ordered and a predicate is monotonic (false...false, true...true).
Typical prompts:
- target in sorted array
- first/last position
- minimum feasible speed/capacity/day
- Use half-open interval
[lo, hi)whenever possible. - Maintain meaning: answer is always in current interval.
Each step halves the search space while preserving the invariant.
- Midpoint:
lo + (hi - lo) / 2. - For "binary search on answer", predicate must be monotonic; prove this first.
- Time: O(log n)
- Space: O(1)
Use pointer rewiring patterns for reverse/merge/remove/cycle operations.
Typical prompts:
- reverse list
- merge sorted lists
- remove nth from end
- cycle detection
- reorder list
- Keep stable handles (
prev,cur,next) during rewiring. - Dummy head simplifies edge cases near the real head.
Pointer operations are local; correctness depends on not losing next references.
Option<Box<ListNode>>rewiring needstake()to move ownership safely.- For custom Rc/RefCell variants, keep borrow scopes short.
- Time: O(n)
- Space: O(1) iterative, O(n) recursive stack if recursion used.
Use DFS for path/structure computations and BFS for level-wise behavior.
Typical prompts:
- depth/diameter/path sum
- BST validation / kth smallest
- level order / right side view
- serialize/deserialize
- DFS returns a well-defined summary for subtree (height, validity, best path, etc.).
- BFS queue contains exactly current frontier.
Trees are recursively defined; post-order DFS naturally composes child summaries.
- Standard LC shape:
Option<Rc<RefCell<TreeNode>>>. - Clone
Rcas needed, but avoid holding mutable and immutable borrows simultaneously.
- Most traversals: O(n) time, O(h) recursion stack (or O(w) queue for BFS).
Use graph traversals for connectivity, shortest path, ordering, and component counts.
Typical prompts:
- number of islands/components
- course schedule (topological sort)
- shortest path unweighted/weighted
- Unweighted shortest path -> BFS
- Weighted nonnegative shortest path -> Dijkstra
- DAG ordering -> Kahn DFS/toposort
- Connectivity -> DFS/BFS/DSU
Graph algorithms are distinguished by edge semantics (weighted vs unweighted, directed vs undirected, cyclic vs DAG).
- Dijkstra min-heap:
BinaryHeap<(Reverse<dist>, node)>. - For large sparse graphs, adjacency list (
Vec<Vec<(to, w)>>) is standard.
- BFS/DFS: O(V + E)
- Dijkstra with heap: O((V + E) log V)
- Kahn topo: O(V + E)
Use when output requires enumerating combinations/permutations/partitions under constraints.
Typical prompts:
- subsets/permutations
- combination sum
- palindrome partitioning
- N-Queens
pathis always a valid partial construction.- Each recursive frame chooses one decision, explores, then undoes it.
Depth-first enumeration with pruning avoids generating impossible branches.
- Clone
pathonly at result push. - Keep mutable vectors reused across recursion for performance.
- Exponential in general; pruning quality determines practical speed.
Use when the problem has overlapping subproblems + optimal substructure.
Typical prompts:
- count ways
- max/min achievable value
- edit distance / LCS
- decode/partition/interleaving
- Define state (
dp[i],dp[i][j], etc.) - Define transition from smaller states
- Initialize base cases
- Choose fill order consistent with dependencies
Memoization/tabulation ensures each state is solved once.
- Use
Vecwith sentinel values (-1,i32::MAX/2, etc.) for clear unreachable states. - Prefer iterative tabulation where recursion depth may overflow.
- Time/space depend on number of states × transitions per state.
Use when a local best choice can be proven to extend to global optimum.
Typical prompts:
- jump game reachability
- interval scheduling
- gas station
- merge/partition style linear scans
You must justify greedy with one of:
- exchange argument
- staying-ahead argument
- cut property
If proof is unclear, DP may be safer.
- Often O(n) or O(n log n) if sorting required.
Use for overlap, merging, scheduling, and room-count questions.
Typical prompts:
- merge intervals
- insert interval
- erase overlap
- meeting rooms
- Sort by start time first.
- Keep current merged interval and either extend or flush.
- O(n log n) for sorting + O(n) sweep.
Use for parity, uniqueness, masking, counting bits, or arithmetic constraints.
Typical prompts:
- single number
- hamming weight
- reverse bits
- sum without plus
x & (x - 1)removes lowest set bit.x ^ x = 0,x ^ 0 = x(XOR cancellation).- Left/right shifts correspond to multiply/divide by powers of two (with sign caveats).
- Be explicit about integer types before bit ops.
- For bit count loops, avoid undefined assumptions about signed shifts.
Use for dynamic connectivity queries and cycle checks in undirected graphs.
Typical prompts:
- number of components
- redundant connection
- graph valid tree checks
- Each set has a representative root.
findwith path compression flattens tree.unionby size/rank keeps trees shallow.
- Amortized near O(1) per operation, formally O(alpha(n)).
When a solution fails:
- State invariant in one sentence.
- Add asserts/logs at every mutation that should preserve invariant.
- Minimize failing test to smallest counterexample.
- Re-check edge cases:
- empty input
- single element
- all equal
- strictly increasing/decreasing
- max/min numeric bounds
This process is usually faster than randomly patching code.
- Prefer
as_bytes()for ASCII string problems. - Keep borrows short around
RefCell. - Avoid repeated cloning of big vectors in recursion loops.
- Use
sort_unstable()when order stability is irrelevant. - Promote to
i64for accumulation to avoid overflow bugs. - For heaps requiring min behavior, wrap with
Reverse.
Suggested loop:
- Read quick trigger in cheat sheet.
- Read matching section in this textbook file.
- Implement one problem.
- Write 1-2 notes under
/// ## Notesand/// ## Complexityin the problem file. - Run:
make template-checkmake solved-syncmake progress
This compounds understanding instead of just memorizing templates.