Interview prep · Lesson 46 of 62 · about 60 min
Pattern recognition: from problem to technique
Read a problem and name the technique within two minutes. A decision flowchart, the trigger words that give each pattern away, what the input limits rule in and out, and one runnable template sheet with the eleven patterns behind most LeetCode problems.
On this page · 11 sections
Clue 1: the shape of the input#
Ask these in order. The first "yes" is usually the pattern, or at least a strong first guess.
Drawing diagram…
Clue 2: the words in the question#
Problem statements leak their pattern through specific phrases. Learn these pairs the way you would learn vocabulary:
| The problem says | Think | Lesson |
|---|---|---|
| "sorted array", "find the position", "minimum x such that" | binary search | Binary search |
| "pair / triplet that sums to", "in-place", "palindrome" | two pointers | Two pointers |
| "longest / shortest substring or subarray", "at most k" | sliding window | Sliding window |
| "subarray sum equals k", "range sum" (negatives allowed) | prefix sums + hash map | Arrays & prefix sums |
| "seen before", "duplicate", "anagram", "frequency" | hash map or set | Hashing |
| "next greater / smaller", "span", "histogram" | monotonic stack | Stacks & queues |
| "maximum of every window of size k" | monotonic deque | Deques |
| "k-th largest", "top k", "k closest", "merge k sorted" | heap | Heaps |
| "median of a stream" | two heaps | Heaps |
| "cycle in a linked list", "middle node" | fast and slow pointers | Linked lists |
| "all subsets / permutations / combinations", "generate every" | backtracking | Backtracking |
| "number of ways", "minimum cost", "can you reach" with overlapping choices | DP | DP I |
| "shortest path", "fewest steps", "minimum moves" (unweighted) | BFS | Graphs |
| "islands", "regions", "connected" | DFS / BFS or union-find | Union-Find |
| "prerequisites", "order of tasks", "build order" | topological sort | Topological sort |
| "weighted", "cheapest", "network delay" | Dijkstra | Topological sort & Dijkstra |
| "prefix", "starts with", "dictionary of words" | trie | Tries |
| "intervals", "meetings", "overlap" | sort by start, sweep | Intervals & sweep line |
| "numbers 1 to n", "missing", "duplicate" without extra space | cyclic sort | In-place array patterns |
| "XOR", "single number", "power of two" | bit manipulation | Bit manipulation |
Clue 3: the constraints#
The input limits tell you the complexity the interviewer expects, and that rules whole families of patterns in or out. A judge runs roughly 10⁸ simple operations per second.
| n up to | Target | Patterns that fit |
|---|---|---|
| 10–12 | O(n!) | permutations by backtracking |
| 20 | O(2ⁿ) | subsets, bitmask DP |
| 500 | O(n³) | interval DP, Floyd–Warshall |
| 5,000 | O(n²) | 2D DP over two strings, all pairs |
| 10⁵–10⁶ | O(n log n) or O(n) | sorting, heap, binary search, window, two pointers, hashing, BFS |
| values up to 10⁹, n small | O(log V) checks | binary search on the answer, math |
Three problems, recognized out loud#
Here is what the reasoning sounds like when you say it in an interview.
"Given an array of positive integers and a target, return the length of the shortest subarray with sum ≥ target." Contiguous ("subarray"), and asks for the shortest one: sliding window. All values are positive, so adding grows the sum and removing shrinks it. That property is exactly what the window needs. O(n).
"Given an array that may contain negatives, count subarrays with sum exactly k." Contiguous again, but negatives mean shrinking the window can raise the sum, so the window logic breaks. Fall back to prefix sums: the sum of nums[i+1..j] is prefix[j] − prefix[i], so for each j count how many earlier prefixes equal prefix[j] − k. A hash map stores those counts. O(n).
"There are n courses and a list of prerequisite pairs. Can you finish them all?" Prerequisites are dependencies, which form a directed graph. "Can you finish" means "is there no cycle": topological sort. Kahn's algorithm removes nodes with no remaining prerequisites; if every node gets removed, the answer is yes. O(V + E).
When two patterns both fit#
Many problems have more than one valid approach. Name both and choose by complexity and how much code each needs:
| Problem | Option A | Option B | Prefer |
|---|---|---|---|
| k-th largest element | sort, O(n log n) | min-heap of size k, O(n log k) | heap (or quickselect, O(n) average) |
| two sum, unsorted | sort + two pointers, O(n log n) | hash map, O(n) | hash map, which also keeps the original indices |
| number of islands | DFS flood fill | union-find | DFS: shorter to write; union-find if cells are added over time |
| course schedule | DFS with three colors | Kahn's BFS | Kahn: no recursion, and the order falls out of it |
| longest increasing subsequence | O(n²) DP | patience sorting + binary search, O(n log n) | say O(n²) first, then improve |
The template sheet#
These eleven functions are the skeletons behind most LeetCode mediums. Each is the smallest correct version of its pattern. Practice typing them without looking until each one takes under three minutes. In an interview you then spend your time on the problem-specific part, not on the loop structure.
package main
import (
"container/heap"
"fmt"
"slices"
)
// 1. Binary search: first index with nums[i] >= target.
func lowerBound(nums []int, target int) int {
lo, hi := 0, len(nums)
for lo < hi {
mid := lo + (hi-lo)/2
if nums[mid] >= target {
hi = mid
} else {
lo = mid + 1
}
}
return lo
}
// 2. Two pointers: pair in a sorted slice that sums to target.
func twoSumSorted(nums []int, target int) (int, int) {
l, r := 0, len(nums)-1
for l < r {
switch s := nums[l] + nums[r]; {
case s == target:
return l, r
case s < target:
l++
default:
r--
}
}
return -1, -1
}
// 3. Sliding window: longest substring with at most k distinct bytes.
func longestKDistinct(s string, k int) int {
count := map[byte]int{}
left, best := 0, 0
for right := 0; right < len(s); right++ {
count[s[right]]++
for len(count) > k {
count[s[left]]--
if count[s[left]] == 0 {
delete(count, s[left])
}
left++
}
best = max(best, right-left+1)
}
return best
}
// 4. Prefix sum + hash map: subarrays that sum to k (negatives allowed).
func subarraySum(nums []int, k int) int {
seen := map[int]int{0: 1}
sum, count := 0, 0
for _, n := range nums {
sum += n
count += seen[sum-k]
seen[sum]++
}
return count
}
// 5. Monotonic stack: days until a warmer temperature.
func dailyTemperatures(t []int) []int {
res := make([]int, len(t))
var stack []int // indices, temperatures decreasing
for i, x := range t {
for len(stack) > 0 t[stack[len(stack)-1]] < x {
j := stack[len(stack)-1]
stack = stack[:len(stack)-1]
res[j] = i - j
}
stack = append(stack, i)
}
return res
}
// 6. Heap: k-th largest with a min-heap that never holds more than k.
type minHeap []int
func (h minHeap) Len() int { return len(h) }
func (h minHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h minHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *minHeap) Push(x any) { *h = append(*h, x.(int)) }
func (h *minHeap) Pop() any {
old := *h
x := old[len(old)-1]
*h = old[:len(old)-1]
return x
}
func kthLargest(nums []int, k int) int {
h := &minHeap{}
for _, n := range nums {
heap.Push(h, n)
if h.Len() > k {
heap.Pop(h)
}
}
return (*h)[0]
}
// 7. Grid BFS: fewest steps from top-left to bottom-right (1 = wall).
func shortestPath(grid [][]int) int {
m, n := len(grid), len(grid[0])
if grid[0][0] == 1 {
return -1
}
dist := make([][]int, m)
for i := range dist {
dist[i] = make([]int, n)
for j := range dist[i] {
dist[i][j] = -1
}
}
dist[0][0] = 0
queue := [][2]int{{0, 0}}
dirs := [][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}
for len(queue) > 0 {
r, c := queue[0][0], queue[0][1]
queue = queue[1:]
for _, d := range dirs {
nr, nc := r+d[0], c+d[1]
if nr < 0 nr >= m nc < 0 nc >= n grid[nr][nc] == 1 dist[nr][nc] != -1 {
continue
}
dist[nr][nc] = dist[r][c] + 1
queue = append(queue, [2]int{nr, nc})
}
}
return dist[m-1][n-1]
}
// 8. Backtracking: every subset.
func subsets(nums []int) [][]int {
var res [][]int
var path []int
var dfs func(i int)
dfs = func(i int) {
if i == len(nums) {
res = append(res, slices.Clone(path))
return
}
path = append(path, nums[i]) // take nums[i]
dfs(i + 1)
path = path[:len(path)-1] // undo
dfs(i + 1) // skip nums[i]
}
dfs(0)
return res
}
// 9. Union-find: number of connected components.
func countComponents(n int, edges [][2]int) int {
parent := make([]int, n)
for i := range parent {
parent[i] = i
}
var find func(int) int
find = func(x int) int {
if parent[x] != x {
parent[x] = find(parent[x])
}
return parent[x]
}
comps := n
for _, e := range edges {
if a, b := find(e[0]), find(e[1]); a != b {
parent[a] = b
comps--
}
}
return comps
}
// 10. Topological sort (Kahn): can every course be finished?
func canFinish(n int, prereq [][2]int) bool {
adj := make([][]int, n)
indeg := make([]int, n)
for _, p := range prereq {
adj[p[1]] = append(adj[p[1]], p[0])
indeg[p[0]]++
}
var queue []int
for i, d := range indeg {
if d == 0 {
queue = append(queue, i)
}
}
done := 0
for len(queue) > 0 {
u := queue[0]
queue = queue[1:]
done++
for _, v := range adj[u] {
if indeg[v]--; indeg[v] == 0 {
queue = append(queue, v)
}
}
}
return done == n
}
// 11. DP: fewest coins that make amount.
func coinChange(coins []int, amount int) int {
dp := make([]int, amount+1)
for a := 1; a <= amount; a++ {
dp[a] = amount + 1 // "infinity"
for _, c := range coins {
if c <= a {
dp[a] = min(dp[a], dp[a-c]+1)
}
}
}
if dp[amount] > amount {
return -1
}
return dp[amount]
}
func main() {
fmt.Println("1 lowerBound:", lowerBound([]int{1, 3, 5, 7}, 4))
l, r := twoSumSorted([]int{2, 7, 11, 15}, 9)
fmt.Println("2 two pointers:", l, r)
fmt.Println("3 window:", longestKDistinct("eceba", 2))
fmt.Println("4 prefix:", subarraySum([]int{1, -1, 1, 1}, 2))
fmt.Println("5 stack:", dailyTemperatures([]int{73, 74, 75, 71, 69, 72, 76, 73}))
fmt.Println("6 heap:", kthLargest([]int{3, 2, 1, 5, 6, 4}, 2))
fmt.Println("7 bfs:", shortestPath([][]int{{0, 0, 0}, {1, 1, 0}, {0, 0, 0}}))
fmt.Println("8 backtrack:", subsets([]int{1, 2}))
fmt.Println("9 union-find:", countComponents(5, [][2]int{{0, 1}, {1, 2}, {3, 4}}))
fmt.Println("10 topo:", canFinish(2, [][2]int{{1, 0}}), canFinish(2, [][2]int{{1, 0}, {0, 1}}))
fmt.Println("11 dp:", coinChange([]int{1, 2, 5}, 11))
}1 lowerBound: 2
2 two pointers: 0 1
3 window: 3
4 prefix: 2
5 stack: [1 1 4 2 1 1 0 0]
6 heap: 5
7 bfs: 4
8 backtrack: [[1 2] [1] [2] []]
9 union-find: 2
10 topo: true false
11 dp: 3Complexity#
| Template | Time | Space |
|---|---|---|
| lower bound | O(log n) | O(1) |
| two pointers, sorted | O(n) | O(1) |
| sliding window | O(n) | O(k) distinct keys |
| prefix sum + map | O(n) | O(n) |
| monotonic stack | O(n), each index pushed and popped once | O(n) |
| top k with heap | O(n log k) | O(k) |
| grid BFS | O(m·n) | O(m·n) |
| subsets | O(n·2ⁿ) | O(n) recursion, plus the output |
| union-find | about O(α(n)) per operation | O(n) |
| Kahn's topological sort | O(V + E) | O(V + E) |
| coin change DP | O(amount · coins) | O(amount) |
How to practice recognition#
Solving problems grouped by topic trains you to apply a pattern you were already told to use. The interview doesn't tell you. Once you've done the topic lessons, switch to mixed practice:
- Take a problem you haven't seen. Read only the statement and the constraints.
- Within two minutes, write down the pattern and the target complexity.
- Then solve it, or check the solution if you're stuck after 25 minutes.
- Log every wrong guess with the clue you missed ("negatives → not a window"). Your log becomes your personal version of the tables above.
Summary#
- Read three clues before coding: the input's shape, the words in the question, and the constraints.
- Trigger words map to patterns: "sorted" → binary search, "subarray" → window or prefix sums, "next greater" → monotonic stack, "top k" → heap, "prerequisites" → topological sort.
- Constraints set the target complexity: n ≤ 20 allows 2ⁿ, n ≈ 10⁵ needs O(n log n).
- Check that a pattern's assumption holds (positive numbers for windows, monotonic checks for binary search on the answer).
- Keep the eleven templates in your fingers so interview time goes to the problem itself.
Check your understanding
Answer before scrolling back up. If you miss one, reread the matching section, then try again.
Q1."Find the number of subarrays whose sum equals k. Values can be negative." Which pattern?
Q2.n ≤ 20 and the task asks for the best choice among all subsets. What does the limit suggest?
Q3."For each day, how many days until a warmer temperature?" Which pattern?
Q4."Minimize the largest sum when splitting the array into k parts." What is the usual approach?
Q5.Edges arrive one by one and you must keep answering "are a and b connected?". Which structure?
Q6.You need the shortest path in an unweighted grid. Which search?
Practice in Go
Write a table test first, then the solution. On LeetCode, choose Go as the language. Open a hint only after 15 minutes stuck.
Show hint
Pattern: hashing. A set of values seen so far.
- Koko Eating Bananas ↗medium
Show hint
Pattern: binary search on the answer. A speed works if the total hours ≤ h, and every faster speed works too.
Show hint
Pattern: sliding window. The window is valid while length − (most frequent count) ≤ k.
- Car Fleet ↗medium
Show hint
Pattern: sort + monotonic stack. Sort by position from the back and stack arrival times; a slower car ahead absorbs faster ones behind it.
- Course Schedule II ↗medium
Show hint
Pattern: topological sort. Kahn's algorithm; if fewer than n courses come out, there is a cycle.
- Number of Provinces ↗medium
Show hint
Pattern: union-find (or DFS). Count the successful unions and subtract them from n.
- Network Delay Time ↗medium
Show hint
Pattern: Dijkstra. Weighted edges, single source, the answer is the largest shortest distance.
Show hint
Pattern: binary search on the answer between max(nums) and sum(nums); greedily count the parts needed for a cap.
- Mixed drillbuild
Open 10 random LeetCode mediums. For each one, read only the statement and the constraints, write down the pattern within two minutes, then check the official solution or discussion. Keep a log of misses and reread the matching lesson for each.
Finished this lesson?
Mark it done to track your progress. It is saved in this browser only.