Go Guide

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
  1. 01Clue 1: the shape of the input
  2. 02Clue 2: the words in the question
  3. 03Clue 3: the constraints
  4. 04Three problems, recognized out loud
  5. 05When two patterns both fit
  6. 06The template sheet
  7. 07Complexity
  8. 08How to practice recognition
  9. 09Summary
  10. 10Check your understanding
  11. 11Practice in Go

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 saysThinkLesson
"sorted array", "find the position", "minimum x such that"binary searchBinary search
"pair / triplet that sums to", "in-place", "palindrome"two pointersTwo pointers
"longest / shortest substring or subarray", "at most k"sliding windowSliding window
"subarray sum equals k", "range sum" (negatives allowed)prefix sums + hash mapArrays & prefix sums
"seen before", "duplicate", "anagram", "frequency"hash map or setHashing
"next greater / smaller", "span", "histogram"monotonic stackStacks & queues
"maximum of every window of size k"monotonic dequeDeques
"k-th largest", "top k", "k closest", "merge k sorted"heapHeaps
"median of a stream"two heapsHeaps
"cycle in a linked list", "middle node"fast and slow pointersLinked lists
"all subsets / permutations / combinations", "generate every"backtrackingBacktracking
"number of ways", "minimum cost", "can you reach" with overlapping choicesDPDP I
"shortest path", "fewest steps", "minimum moves" (unweighted)BFSGraphs
"islands", "regions", "connected"DFS / BFS or union-findUnion-Find
"prerequisites", "order of tasks", "build order"topological sortTopological sort
"weighted", "cheapest", "network delay"DijkstraTopological sort & Dijkstra
"prefix", "starts with", "dictionary of words"trieTries
"intervals", "meetings", "overlap"sort by start, sweepIntervals & sweep line
"numbers 1 to n", "missing", "duplicate" without extra spacecyclic sortIn-place array patterns
"XOR", "single number", "power of two"bit manipulationBit 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 toTargetPatterns that fit
10–12O(n!)permutations by backtracking
20O(2ⁿ)subsets, bitmask DP
500O(n³)interval DP, Floyd–Warshall
5,000O(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 smallO(log V) checksbinary 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:

ProblemOption AOption BPrefer
k-th largest elementsort, O(n log n)min-heap of size k, O(n log k)heap (or quickselect, O(n) average)
two sum, unsortedsort + two pointers, O(n log n)hash map, O(n)hash map, which also keeps the original indices
number of islandsDFS flood fillunion-findDFS: shorter to write; union-find if cells are added over time
course scheduleDFS with three colorsKahn's BFSKahn: no recursion, and the order falls out of it
longest increasing subsequenceO(n²) DPpatience 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.

templates.go
Go
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))
}
Output
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: 3

Complexity#

TemplateTimeSpace
lower boundO(log n)O(1)
two pointers, sortedO(n)O(1)
sliding windowO(n)O(k) distinct keys
prefix sum + mapO(n)O(n)
monotonic stackO(n), each index pushed and popped onceO(n)
top k with heapO(n log k)O(k)
grid BFSO(m·n)O(m·n)
subsetsO(n·2ⁿ)O(n) recursion, plus the output
union-findabout O(α(n)) per operationO(n)
Kahn's topological sortO(V + E)O(V + E)
coin change DPO(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:

  1. Take a problem you haven't seen. Read only the statement and the constraints.
  2. Within two minutes, write down the pattern and the target complexity.
  3. Then solve it, or check the solution if you're stuck after 25 minutes.
  4. 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.

  1. Q1."Find the number of subarrays whose sum equals k. Values can be negative." Which pattern?

  2. Q2.n ≤ 20 and the task asks for the best choice among all subsets. What does the limit suggest?

  3. Q3."For each day, how many days until a warmer temperature?" Which pattern?

  4. Q4."Minimize the largest sum when splitting the array into k parts." What is the usual approach?

  5. Q5.Edges arrive one by one and you must keep answering "are a and b connected?". Which structure?

  6. 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.

  • 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.

  • 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.

  • Show hint

    Pattern: topological sort. Kahn's algorithm; if fewer than n courses come out, there is a cycle.

  • Show hint

    Pattern: union-find (or DFS). Count the successful unions and subtract them from n.

  • 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.