Coding Patterns — Cheat Sheet
DSA Patterns · 13 topics. Download the PDF or the Instagram carousel and share it.
Sliding Window
Process a window of elements that slides across an array or string.
- ✓Problem involves a contiguous subarray or substring
- ✓Looking for a maximum, minimum, or count within a window
- ✓Keywords: "longest", "shortest", "contains", "subarray", "substring"
- ✓Window size is either fixed or dynamic based on a condition
- ✓Instead of recomputing the entire window on every step, maintain a running state (sum, freq map, etc.) and update it incrementally as the window slides.
// Fixed-size window
int windowSum = 0, maxSum = 0;
for (int i = 0; i < nums.length; i++) {
windowSum += nums[i];
if (i >= k - 1) {
maxSum = Math.max(maxSum, windowSum);
windowSum -= nums[i - (k - 1)];
}
}
// Variable-size window (two pointers)
int left = 0, result = 0;
Map<Character, Integer> freq = new HashMap<>();
for (int right = 0; right < s.length(); right++) {
freq.merge(s.charAt(right), 1, Integer::sum);
// shrink window while condition violated
while (freq.size() > k) {
char c = s.charAt(left++);
freq.merge(c, -1, Integer::sum);
if (freq.get(c) == 0) freq.remove(c);
}
result = Math.max(result, right - left + 1);
}Two Pointers
Use two indices to scan from both ends or at different speeds.
- ✓Array or string is sorted (or can be sorted)
- ✓Looking for a pair, triplet, or partition
- ✓Need to remove duplicates or reverse in-place
- ✓Keywords: "two sum", "palindrome", "partition", "opposite ends"
- ✓Move the left pointer right when the current sum is too small, move the right pointer left when too large. The sorted order guarantees you never need to revisit elements.
// Opposite-direction two pointers (sorted array)
int left = 0, right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
// found pair
left++; right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
// Same-direction two pointers (in-place remove)
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != val) {
nums[slow++] = nums[fast];
}
}Fast & Slow Pointers
Floyd's cycle detection — two pointers at different speeds.
- ✓Detecting a cycle in a linked list or sequence
- ✓Finding the start of a cycle
- ✓Finding the middle of a linked list
- ✓Detecting a duplicate without extra space
- ✓When fast and slow meet inside a cycle, reset slow to head. Moving both one step at a time from there brings them to the cycle entrance — a mathematical property of the meeting point.
// Detect cycle
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true; // cycle exists
}
return false;
// Find cycle start
if (hasCycle) {
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
// slow == fast == cycle start
}
// Find middle of linked list
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// slow is at middleBinary Search
Halve the search space every step on any monotonic condition.
- ✓Array is sorted or partially sorted
- ✓Finding a boundary (first/last occurrence)
- ✓Answer is a number and feasibility is monotonic ("can I do it in X days?")
- ✓Looking for a peak, minimum in rotated, or kth element
- ✓Always define: what does mid represent, and which half can I eliminate? The three templates — find exact, find leftmost, find rightmost — cover all cases. Get the loop invariant right and the off-by-one errors disappear.
// Classic binary search
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
else if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
// Binary search on answer (e.g. "minimum capacity to ship in D days")
int left = minCapacity, right = maxCapacity;
while (left < right) {
int mid = left + (right - left) / 2;
if (feasible(mid)) right = mid; // mid works, try smaller
else left = mid + 1; // mid fails, need larger
}
return left; // smallest feasible valueTree Traversal
Visit every node in a tree — recursively (DFS) or level-by-level (BFS).
- ✓DFS: depth, path sum, subtree comparison, LCA
- ✓BFS: level order, minimum depth, right side view
- ✓Inorder on BST gives sorted sequence
- ✓Postorder when you need subtree results before parent
- ✓For most DFS tree problems, define a recursive function that returns something meaningful (height, count, bool) and propagates results bottom-up. Trust the recursion — you only need to handle the current node and its two children.
// DFS — recursive (postorder example)
int height(TreeNode node) {
if (node == null) return 0;
int left = height(node.left);
int right = height(node.right);
return 1 + Math.max(left, right);
}
// BFS — level order
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
int size = q.size(); // nodes at current level
for (int i = 0; i < size; i++) {
TreeNode node = q.poll();
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
}
}Graph BFS / DFS
Explore all reachable nodes in a graph, tracking visited state.
- ✓Counting connected components or islands
- ✓Shortest path in unweighted graph → BFS
- ✓Topological sort / dependency ordering → DFS or BFS (Kahn's)
- ✓Cycle detection in directed graph → DFS with color states
- ✓Multi-source BFS when starting from multiple nodes simultaneously
- ✓Mark a cell/node visited BEFORE pushing it onto the queue (BFS), not after popping — otherwise you push duplicates and get TLE. For DFS cycle detection, use three states: unvisited (0), in-stack (1), done (2).
// BFS — shortest path / multi-source
boolean[][] visited = new boolean[rows][cols];
Queue<int[]> q = new LinkedList<>();
q.offer(new int[]{startR, startC});
visited[startR][startC] = true;
int steps = 0;
int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
while (!q.isEmpty()) {
int size = q.size();
for (int i = 0; i < size; i++) {
int[] cur = q.poll();
for (int[] d : dirs) {
int nr = cur[0] + d[0], nc = cur[1] + d[1];
if (inBounds(nr, nc) && !visited[nr][nc] && grid[nr][nc] == 1) {
visited[nr][nc] = true;
q.offer(new int[]{nr, nc});
}
}
}
steps++;
}
// DFS — cycle detection (directed graph)
// 0 = unvisited, 1 = in-stack, 2 = done
int[] state = new int[n];
boolean hasCycle(int node, List<List<Integer>> adj) {
state[node] = 1;
for (int nei : adj.get(node)) {
if (state[nei] == 1) return true;
if (state[nei] == 0 && hasCycle(nei, adj)) return true;
}
state[node] = 2;
return false;
}Dynamic Programming
Break the problem into overlapping subproblems and cache results.
- ✓Counting ways, maximizing/minimizing a value, or checking feasibility
- ✓Problem can be broken into overlapping subproblems
- ✓Keywords: "minimum cost", "maximum profit", "number of ways", "can you reach"
- ✓Greedy doesn't work — local optimal ≠ global optimal
- ✓Define dp[i] clearly before writing any code. Ask: "what does dp[i] represent?" Then write the recurrence and base cases. The transition almost always looks at dp[i-1] or dp[i-w] or dp[i][j-1].
// 1D DP — climbing stairs / coin change
int[] dp = new int[n + 1];
dp[0] = 1; // base case
for (int i = 1; i <= n; i++) {
for (int coin : coins) {
if (i >= coin) dp[i] += dp[i - coin];
}
}
// 2D DP — LCS / edit distance
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s1.charAt(i-1) == s2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = 1 + Math.min(dp[i-1][j], Math.min(dp[i][j-1], dp[i-1][j-1]));
}
}
}Backtracking
Explore all possibilities by building candidates and pruning dead ends.
- ✓Generating all subsets, permutations, or combinations
- ✓Grid/string problems asking to "find all valid paths"
- ✓Constraint satisfaction (N-Queens, Sudoku)
- ✓Keywords: "all possible", "generate all", "find all combinations"
- ✓The pattern is always: choose → recurse → unchoose. The "unchoose" step (removing the last element from your path) is what makes backtracking distinct from plain DFS. Always pass a start index to avoid re-using elements.
// Subsets / Combinations template
List<List<Integer>> result = new ArrayList<>();
void backtrack(int start, List<Integer> current, int[] nums) {
result.add(new ArrayList<>(current)); // add a copy
for (int i = start; i < nums.length; i++) {
current.add(nums[i]); // choose
backtrack(i + 1, current, nums); // recurse
current.remove(current.size() - 1); // unchoose
}
}
// Permutations template
void permute(int[] nums, List<Integer> current, boolean[] used) {
if (current.size() == nums.length) {
result.add(new ArrayList<>(current));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) continue;
used[i] = true;
current.add(nums[i]);
permute(nums, current, used);
current.remove(current.size() - 1);
used[i] = false;
}
}Monotonic Stack
A stack maintained in sorted order to find next greater/smaller elements.
- ✓Finding the next greater or smaller element for each position
- ✓Calculating spans, areas, or distances based on surrounding elements
- ✓Keywords: "next greater", "daily temperatures", "histogram", "trapping water"
- ✓Processing elements where future elements affect past calculations
- ✓Elements are pushed and popped at most once → O(n) total. For "next greater to the right", iterate left-to-right with a decreasing stack. For "previous smaller", iterate right-to-left or use an increasing stack.
// Next Greater Element (monotonic decreasing stack)
int[] result = new int[nums.length];
Arrays.fill(result, -1);
Deque<Integer> stack = new ArrayDeque<>(); // stores indices
for (int i = 0; i < nums.length; i++) {
// pop elements smaller than current — current is their answer
while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
result[stack.pop()] = nums[i];
}
stack.push(i);
}
// Largest Rectangle in Histogram pattern
stack.push(-1); // sentinel
for (int i = 0; i <= heights.length; i++) {
int h = (i == heights.length) ? 0 : heights[i];
while (stack.peek() != -1 && heights[stack.peek()] > h) {
int height = heights[stack.pop()];
int width = i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}Merge Intervals
Sort intervals by start time, then sweep and merge overlapping ones.
- ✓Given a list of intervals, find overlaps or merge them
- ✓Scheduling / meeting room problems
- ✓Inserting a new interval into a sorted list
- ✓Finding minimum number of groups/rooms to cover all intervals
- ✓Two intervals [a,b] and [c,d] overlap if and only if c ≤ b (after sorting by start). The merged interval is [a, max(b, d)]. Always sort first — the sweep only works in order.
// Merge overlapping intervals
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
List<int[]> merged = new ArrayList<>();
merged.add(intervals[0]);
for (int i = 1; i < intervals.length; i++) {
int[] last = merged.get(merged.size() - 1);
if (intervals[i][0] <= last[1]) {
// overlap — extend the end
last[1] = Math.max(last[1], intervals[i][1]);
} else {
merged.add(intervals[i]);
}
}
// Count minimum rooms (or groups) needed
Arrays.sort(starts); Arrays.sort(ends);
int rooms = 0, maxRooms = 0, j = 0;
for (int i = 0; i < n; i++) {
if (starts[i] < ends[j]) { rooms++; maxRooms = Math.max(maxRooms, rooms); }
else { rooms--; j++; }
}Top K Elements
Use a heap to track the K largest or smallest elements in O(n log k).
- ✓Finding kth largest or kth smallest element
- ✓Top K frequent elements
- ✓Merging K sorted lists
- ✓Finding the median of a stream (two heaps)
- ✓Keywords: "k largest", "k most frequent", "k closest"
- ✓Min-heap of size k → gives you k largest (root = kth largest). Max-heap of size k → gives you k smallest (root = kth smallest). Always ask: which end of the heap do I want to evict from?
// Kth Largest — min-heap of size k
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : nums) {
minHeap.offer(num);
if (minHeap.size() > k) minHeap.poll(); // remove smallest
}
return minHeap.peek(); // kth largest
// Top K Frequent — bucket approach or heap
Map<Integer, Integer> freq = new HashMap<>();
for (int n : nums) freq.merge(n, 1, Integer::sum);
PriorityQueue<Integer> heap = new PriorityQueue<>(
(a, b) -> freq.get(a) - freq.get(b) // min-heap by frequency
);
for (int key : freq.keySet()) {
heap.offer(key);
if (heap.size() > k) heap.poll();
}Linked List In-Place
Reverse, merge, or restructure linked lists without extra space.
- ✓Reversing all or part of a linked list
- ✓Merging two sorted lists or K sorted lists
- ✓Rearranging nodes (odd-even, rotate, reorder)
- ✓Deep copying a list with random pointers
- ✓Always draw 3-4 nodes on paper and trace pointer changes before coding. Keep a 'prev' pointer when reversing. Use a dummy head node to handle edge cases at the start of the list cleanly.
// Reverse a linked list (iterative)
ListNode prev = null, curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev; // new head
// Merge two sorted lists
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) { tail.next = l1; l1 = l1.next; }
else { tail.next = l2; l2 = l2.next; }
tail = tail.next;
}
tail.next = (l1 != null) ? l1 : l2;
return dummy.next;Trie (Prefix Tree)
A tree for storing strings character-by-character for fast prefix lookups.
- ✓Prefix matching or autocomplete
- ✓Storing a dictionary of words for repeated lookups
- ✓Word search in a 2D board (Trie prunes dead-end paths)
- ✓Finding longest common prefix across many strings
- ✓Each TrieNode holds children[26] (for lowercase letters) and a boolean isEnd. Insert: for each char, create child if absent then move down. Search: traverse and check isEnd. StartsWith: traverse and return true if path exists.
class TrieNode {
TrieNode[] children = new TrieNode[26];
boolean isEnd = false;
}
class Trie {
private TrieNode root = new TrieNode();
public void insert(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int idx = c - 'a';
if (node.children[idx] == null)
node.children[idx] = new TrieNode();
node = node.children[idx];
}
node.isEnd = true;
}
public boolean search(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int idx = c - 'a';
if (node.children[idx] == null) return false;
node = node.children[idx];
}
return node.isEnd;
}
public boolean startsWith(String prefix) {
TrieNode node = root;
for (char c : prefix.toCharArray()) {
int idx = c - 'a';
if (node.children[idx] == null) return false;
node = node.children[idx];
}
return true;
}
}