Leetcode
This note is meant to be a high level playbook for solving LeetCode-style problems. The goal is not to catalog every trick, but to build a repeatable process for recognizing the underlying structure of a problem and selecting a strong method quickly.
I generally prefer:
- Optimal or near-optimal asymptotic solutions
- Methods that generalize across many problems
- Solutions built around a clean invariant or state definition
- Avoiding one-off tricks unless the problem forces them
General Process
1. Classify the input and the output
Before thinking about code, classify what kind of object the problem gives you:
- Array or string
- Matrix / grid
- Linked list
- Tree
- Graph
- Interval set
- Implicit state space
Then classify what the output is asking for:
- Existence: does some solution exist?
- Decision: true / false
- Count: how many ways / objects?
- Optimization: min / max / shortest / cheapest
- Construction: return the path / ordering / structure / subsets
This usually narrows the candidate methods a lot.
2. Look for the strongest constraint first
The desired time complexity is usually hinted at by the input size.
n <= 20: brute force, subsets, bitmasking, backtracking are often acceptablen <= 1000:O(n^2)may be fine,O(n log n)is usually goodn <= 10^5or larger: usually needO(n)orO(n log n)
If the prompt says “can you do better than O(n^2)?” it is almost always pointing to a standard pattern, not a niche trick.
3. Ask what information must be remembered
Most problems reduce to one of these questions:
- Do I need fast membership lookup? Use a hash set / map.
- Do I need a moving valid region? Use a sliding window.
- Do I need repeated best prefix / suffix information? Use DP, prefix sums, or Kadane-style state.
- Do I need repeated exploration from a node or cell? Use DFS / BFS.
- Do I need the next smallest / largest item repeatedly? Use a heap.
- Do I need ordered access or range splitting? Use sorting or binary search.
4. Define the invariant before the implementation
Most good solutions become easy once the invariant is clear.
Examples:
- Sliding window: “window
[l, r]is always valid” - Binary search: “answer is always inside
[l, r)” - DP: “
dp[i]means the best / number of ways / feasibility for prefix or suffixi” - BFS: “everything currently in the queue is at the current distance or level”
- DFS on trees: “the recursive return value is exactly the information the parent needs”
If you cannot state the invariant in one sentence, you probably do not understand the method yet.
5. Decide whether the problem is local or global
Local structure often suggests greedy or two pointers. Global dependence often suggests DP, graph search, or recursion.
- Local: merging intervals, meeting rooms, container width decisions
- Global: word segmentation, cheapest constrained path, tree reconstruction
Pattern Recognition by Problem Type
Arrays and strings
These are the most pattern-heavy problems. The first things to check are:
- Hashing for membership or frequency
- Two pointers for monotone movement
- Sliding window for contiguous subarrays / substrings
- Prefix sums for range aggregates
- Sorting when relative order matters more than original position
- Binary search when there is a monotone property
Hash map / hash set
Reach for hashing when you need:
- Fast existence checks
- Frequency counting
- Matching complements
- Deduplication
Examples:
Typical signal phrases:
- “have we seen this before?”
- “find two values that combine to …”
- “count frequency”
- “unique elements”
Two pointers
Use two pointers when both ends can move monotonically and you do not need to revisit positions.
Common cases:
- Input already sorted
- Need to shrink or expand a range
- Need to compare symmetric characters or endpoints
- Need to merge or partition in one pass
Examples:
Strong signal: moving one pointer can only make one quantity better and the other worse, so you can eliminate states safely.
Sliding window
Use sliding window when the answer is a contiguous region and validity can be maintained incrementally.
Good signals:
- Subarray / substring
- Longest / shortest contiguous block satisfying a condition
- At most / at least
kdistinct, replacements, budget, frequency limit
Examples:
- Longest Substring Without Repeating Characters
- Longest Repeating Character Replacement
- Minimum Window Substring
The key question is whether adding one element and removing one element lets you update the state without recomputing from scratch.
Prefix sums
Use prefix sums when many range queries can be answered from cumulative information.
Typical form:
prefix[i]stores information about everything beforei- A range is computed by subtracting two prefixes
This extends beyond sums to counts, parity, balances, and transformed values.
Sorting
Sort when the actual values matter more than original order, especially for:
- Intervals
- Greedy scheduling
- Sweep line logic
- Grouping nearby values
Examples:
If sorting creates a left-to-right structure where each decision only depends on what has already been processed, that is usually a good sign.
Binary search
Use binary search when there is a monotone predicate or partially sorted structure.
Two common forms:
- Search for an element in structured data
- Search for the smallest / largest feasible answer
Examples:
Ask:
- If I guess an answer
x, can I efficiently check whetherxis feasible? - Does feasibility only switch once from false to true, or true to false?
Safe patterns:
- Use
m = low + (high - low) / 2orstd::midpoint(low, high)to avoid overflow - If your loop is
while (low < high), updates must keep shrinking the window without stalling - A safe lower-mid template is:
while (low < high) {
int m = low + (high - low) / 2;
if (predicate(m)) {
high = m;
} else {
low = m + 1;
}
}This is safe because high = m keeps m since it may be the answer, and low = m + 1 guarantees progress. The dangerous pattern with lower mid is low = m, since when m == low the loop can stall forever.
Rule of thumb:
- Use
while (low < high)when the answer is guaranteed to exist and you are narrowing to a single boundary value, such as Find Minimum in Rotated Sorted Array - Use
while (low <= high)when searching for a target that may not exist, since you want to fully exhaust the candidate range before returning-1, such as Search in Rotated Sorted Array
For target search, a safe template is:
while (low <= high) {
int m = low + (high - low) / 2;
if (nums[m] == target) {
return m;
}
if (target < nums[m]) {
high = m - 1;
} else {
low = m + 1;
}
}
return -1;Small comparison rule:
- Use inclusive comparisons when describing the current candidate interval or its structure, e.g.
nums[l] <= nums[m]means the half[l, m]is sorted - Use strict comparisons against
maftermhas already been checked and ruled out, e.g. afterif (nums[m] == target), usetarget < nums[m]ortarget > nums[m] - In rotated search, this is why
nums[l] <= nums[m]is correct for identifying the sorted half, butnums[l] <= target && target < nums[m]is correct for checking whether the target lies in that half
Intervals
Intervals are usually one of:
- Merge overlaps
- Count concurrent intervals
- Choose non-overlapping intervals optimally
- Insert one interval into a sorted structure
The standard approach is almost always sorting by start or end, then scanning.
Examples:
Useful heuristics:
- Sort by start if you need to merge or compare neighbors
- Sort by end if you want a greedy maximum number of non-overlapping choices
- Use a heap if you need the earliest finishing active interval repeatedly
- Use a sweep line if you only need active counts over time
Linked lists
Linked list problems are usually pointer-manipulation problems. The main reusable ideas are:
- Dummy head
- Slow / fast pointers
- Reverse a segment
- Split and merge
- Cycle detection
Examples:
- Reverse Linked List
- Remove Nth Node From End of List
- Reorder Linked List
- Linked List Cycle Detection
Common signals:
- “from end” often suggests two pointers with a gap
- “cycle” suggests Floyd’s tortoise and hare
- “reorder” often means split, reverse second half, merge alternating
Trees
Tree problems are often best solved by asking:
- Is this naturally a traversal problem?
- Does the parent need information from children?
- Do I need DFS, BFS, or BST ordering?
DFS on trees
DFS is the default for most tree problems. The main question is what the recursive call should return.
Examples:
Useful mindset:
- Define what
dfs(node)returns - Make that return value be exactly what the parent needs
- Keep any global answer separate if the local return and global optimum are different
For example, in maximum path sum, the value returned upward is not the same as the best path anywhere in the subtree.
BFS on trees
Use BFS when the problem is about levels, shortest unweighted distance, or nearest layer-by-layer behavior.
Example:
BST structure
If the problem is on a binary search tree, use the ordering immediately.
Examples:
Whenever you see BST, ask whether you can avoid exploring both subtrees by comparing values.
Tree construction
Construction problems usually combine recursion with index windows or maps.
Example:
The general pattern is:
- One traversal tells you the root order
- Another tells you subtree boundaries
- A map avoids repeated searches
Graphs
Many problems are graphs even when they are not presented that way.
Hidden graph signals:
- Courses with prerequisites
- Words connected by transformations
- Cells connected by moves
- States reachable by operations
Once you see a graph, the next question is what kind:
- Unweighted reachability: DFS / BFS
- Unweighted shortest path: BFS
- DAG ordering: topological sort
- Weighted shortest path with non-negative weights: Dijkstra
- Limited number of edges / transitions: DP or layered BFS / Bellman-Ford style relaxation
- Connected components: DFS / BFS / Union-Find
DFS / BFS for reachability and components
Examples:
Use DFS when recursion makes the structure easy to express. Use BFS when distance in edges matters or the process is naturally layer-based.
Topological sort
Use topological sort for dependency problems on DAGs.
Examples:
Typical signal phrases:
- “must happen before”
- “ordering consistent with constraints”
- “can all tasks be finished?”
Indegree + queue is usually the most reusable approach.
Shortest path
Decide based on the edge model:
- All edges equal weight: BFS
- Non-negative weights: Dijkstra
- Bounded number of edges / stops: DP relaxation can be cleaner
Example:
This is a good example of not forcing Dijkstra when the constraint is really on the number of edges used. The state of the problem matters more than the classical graph label.
Matrices and grids
Grid problems are usually one of:
- Flood fill / connected component search
- Multi-source BFS
- DFS backtracking path search
- DP over rows / columns
Examples:
Quick recognition rules:
- “spread”, “minimum minutes”, “nearest”, “fewest moves” often means BFS
- “does a path exist following rules?” often means DFS / backtracking
- “count paths” often means DP
Dynamic programming
DP is appropriate when:
- There are overlapping subproblems
- A brute force recursion would recompute the same states many times
- The answer for a large instance can be built from smaller instances
Examples:
How to recognize DP
Ask these questions:
- Is there a natural prefix, suffix, index, or state parameter?
- If I write the obvious recursion, do many calls repeat?
- Does the choice at one step leave me with a smaller problem of the same form?
How to design the state
Most DP work is state design. Common state types:
- Prefix index: first
ielements - Suffix index: from
ionward - Two indices: substring / subsequence problems
- Position plus extra condition: remaining sum, remaining capacity, previous choice, number of steps used
The state should be just enough information to make the future independent of the past.
Common DP families
1-D linear DP
Examples:
These often have the form:
- Take current
- Skip current
- Transition from a small fixed number of previous states
Prefix / segmentation DP
Examples:
Signal: “can the prefix up to i be formed?” or “how many ways can suffix i: be parsed?”
2-D sequence DP
Examples:
Signal: comparing two sequences or solving over substring boundaries.
Knapsack-style DP
Examples:
- Coin Change
- Combination Sum can also be viewed through this lens, although backtracking is the more direct pattern there
Signal: choices consume some resource such as sum, weight, or capacity.
Backtracking
Use backtracking when you need to enumerate candidates under constraints.
Examples:
Recognition signals:
- Return all combinations / subsets / permutations / paths
- Constraint violations let you prune early
- The space of possibilities forms a search tree
Backtracking template:
- Choose a candidate
- Recurse
- Undo the choice
The important part is pruning. Without pruning, backtracking often becomes too slow.
Greedy
Greedy is appropriate when a locally optimal choice can be shown not to hurt the global optimum.
Examples:
Good greedy signals:
- Need only one scalar summary of everything seen so far
- Earlier decisions can be proven to dominate worse ones
- Sorting creates a canonical order where the best current choice is obvious
Greedy should usually come with a proof sketch, even if informal. If you cannot explain why the local choice is safe, DP may be the more reliable model.
Heaps
Use a heap when you repeatedly need the current smallest or largest item under updates.
Examples:
Typical signals:
- “top k”
- “stream”
- “next smallest” while items are being inserted
- Need an online algorithm
Choosing Between Similar Techniques
Sliding window vs two pointers
Use sliding window when you maintain a contiguous region with a validity condition.
Use general two pointers when the two indices move for a structural reason, often on sorted arrays or opposite ends.
DFS vs BFS
Use DFS when:
- You only need existence, counting, structure, or postorder information
- Recursion makes the relation natural
Use BFS when:
- Shortest path in an unweighted graph matters
- Layer / distance / minute / step count matters
- You want nearest-first processing
DP vs backtracking
Use backtracking when you need to enumerate actual solutions.
Use DP when you only need existence, count, or optimum and subproblems overlap heavily.
DP vs greedy
Try greedy if the problem has a strong ordering and a local choice seems dominant.
If choices interact in a way that can invalidate a local decision later, DP is safer.
Sorting vs hashing
Use hashing for O(1) average lookup when order is irrelevant.
Use sorting when relative order or adjacency after ordering exposes the structure.
A Practical Checklist
When I see a new problem, I usually go through this checklist:
1. What is the object?
Array, string, tree, graph, interval set, grid, linked list?
2. What is being asked?
Existence, count, optimization, or construction?
3. What complexity is realistic?
From the constraints, which classes of algorithms are even allowed?
4. Is there a standard pattern signal?
- Subarray / substring: sliding window or prefix sums
- Dependency ordering: topological sort
- Connected cells: DFS / BFS
- Min / max over choices with overlap: DP
- Sorted / rotated / monotone: binary search
- Overlapping intervals: sort and scan
5. What should the state or invariant be?
Can I state in one sentence what my window, DP table, queue, or recursion means?
6. Can I solve a smaller version of the same problem?
If yes, recursion or DP is likely nearby.
7. Can I discard possibilities safely?
If yes, that usually points to greedy, two pointers, binary search, or pruning in backtracking.
Common Pitfalls
Forcing the wrong pattern
A problem may contain strings but really be a graph problem, or contain a grid but really be a shortest path problem.
Writing code before defining state
Most bugs in DP, sliding window, and graph traversal come from unclear state semantics.
Ignoring edge cases implied by the invariant
Examples:
- Empty input
- Single element
- Duplicate values
- Inclusive vs exclusive interval boundaries
- Revisiting nodes or cells
- Whether touching intervals count as overlap
Using a clever trick instead of a robust pattern
A one-off trick may pass a single problem but not build reusable instinct. Prefer a method you can explain and reuse unless the problem clearly demands something more specialized.
Final Heuristic
When in doubt, reduce the problem to one of these repeatable questions:
- Can I scan once while maintaining a valid window?
- Can I sort first so the structure becomes obvious?
- Can I model it as a graph and traverse it?
- Can I define a DP state that captures the remaining work?
- Can I make a local greedy choice and justify why it is safe?
- Can I search over the answer because feasibility is monotone?
If one of those questions has a clean answer, that is usually the method to grab first.
Pattern Lookup Table
Common signals
| Signal in problem statement | Likely technique | Canonical examples |
|---|---|---|
| “find two values that sum / combine to target” | Hash map, sometimes two pointers after sorting | Two Sum, 3Sum |
| “longest / shortest substring or subarray satisfying condition” | Sliding window | Longest Substring Without Repeating Characters, Minimum Window Substring |
“sorted array”, “rotated sorted array”, “find target in O(log n)” | Binary search | 74 - Search a 2D Matrix, Search in Rotated Sorted Array |
| “overlapping intervals”, “minimum rooms”, “schedule meetings” | Sorting + scan, heap, or sweep line | Merge Intervals, Meeting Rooms II |
| “tree level by level” | BFS | Binary Tree Level Order Traversal |
| “parent depends on children” | DFS on trees | Maximum Depth of Binary Tree, Binary Tree Maximum Path Sum |
| “BST” | Use ordering to prune search | Lowest Common Ancestor in Binary Search Tree, Kth Smallest Integer in BST |
| “can finish all tasks”, “dependency order”, “prerequisites” | Topological sort | Course Schedule, Alien Dictionary |
| “connected components”, “islands”, “flood fill” | DFS / BFS / Union-Find | Number of Islands, Max Area of Island |
| “fewest steps”, “minimum moves”, “spread over time” in unweighted graph or grid | BFS | Rotting Fruit |
| “cheapest / shortest path” with weighted edges | Dijkstra, or DP if edge count is constrained | Cheapest Flights Within K Stops |
| “count ways”, “can segment”, “best value with repeated overlapping choices” | Dynamic programming | Word Break, Decode Ways, House Robber |
| “return all combinations / paths / subsets” | Backtracking | Combination Sum, Word Search |
| “stream of values”, “top k”, “repeatedly get min / max” | Heap | Find Median From Data Stream, Merge K Sorted Linked Lists |
| “linked list cycle”, “middle node”, “nth from end” | Slow / fast pointers | Linked List Cycle Detection, Remove Nth Node From End of List |
Quick decision rules
| If the problem feels like… | Check this first |
|---|---|
| A contiguous region with a validity condition | Sliding window |
| A repeated best choice among active candidates | Heap |
| A dependency graph | Topological sort |
| A search over reachable states | DFS / BFS |
| An optimization over overlapping subproblems | DP |
| A local choice might safely dominate worse ones | Greedy |
| Ordering the data may expose the structure | Sorting |
| The answer space is monotone under a feasibility test | Binary search on answer |