· 8 min read

LeetCode Patterns: Question → Solution Cheat Sheet (with JS templates)

Map any LeetCode question to the right pattern fast. Concrete triggers, minimal code templates, and pitfalls.

If you struggle recognizing what a question is really asking for, use this pattern-first mapping. Scan the triggers, pick the pattern, drop in the template, and fill the condition.

Quick Map: When you see X, reach for Y

  • Find longest/shortest subarray/string with constraint → Sliding Window
  • Sum/avg/min/max over contiguous range, window size k → Sliding Window (fixed)
  • Pair/triplet in sorted array; palindrome; converge from ends → Two Pointers (opposite)
  • In-place dedupe/partition; fast vs slow index → Two Pointers (same direction)
  • Check existence/count of subarray with sum k → Prefix Sum + HashMap
  • Next greater/smaller element; ranges to the next drop/rise → Monotonic Stack
  • Kth / Top-K → Heap(s)
  • Intervals: merge, insert, min rooms, non-overlap → Sort + Greedy / Sweep Line / Heap
  • Binary answer (can we do X with Y?), minimize max, maximize min → Binary Search on Answer
  • Graph shortest path (unweighted) → BFS; DAG → Topo Sort + DP
  • Tree traversals, LCA, views → DFS/BFS templates
  • Cycle detection; find middle; reorder → Fast/Slow Pointers (linked list)
  • Count islands / connected components → DFS/BFS; big constraints → Union-Find
  • DP on sequences (LIS, edit distance, knapsack) → 1D/2D DP
  • Generate all combos/perms/subsets; constraints check → Backtracking with pruning
  • Greedy exchange/invariant; intervals; jump → Greedy (prove by exchange or invariant)
  • Bitwise constraints; exactly once except one → XOR / bit counts

Sliding Window

Use for contiguous subarrays/strings under a constraint.

Fixed-size window (size k)

function maxSumOfSizeK(nums, k) {
  let sum = 0;
  for (let i = 0; i < k; i++) sum += nums[i];
  let best = sum;
  for (let r = k; r < nums.length; r++) {
    sum += nums[r] - nums[r - k];
    if (sum > best) best = sum;
  }
  return best;
}

Variable-size window (shrink to satisfy constraint)

function longestWithAtMostKDistinct(s, k) {
  const freq = new Map();
  let left = 0,
    best = 0;
  for (let right = 0; right < s.length; right++) {
    freq.set(s[right], (freq.get(s[right]) ?? 0) + 1);
    while (freq.size > k) {
      const c = s[left++];
      const n = (freq.get(c) ?? 0) - 1;
      if (n <= 0) freq.delete(c);
      else freq.set(c, n);
    }
    if (right - left + 1 > best) best = right - left + 1;
  }
  return best;
}

Two Pointers

Opposite direction (sorted arrays, palindrome)

function twoSumSorted(nums, target) {
  let l = 0,
    r = nums.length - 1;
  while (l < r) {
    const sum = nums[l] + nums[r];
    if (sum === target) return [l + 1, r + 1];
    if (sum < target) l++;
    else r--;
  }
  return [];
}

Same direction (in-place transform)

function removeDuplicatesSorted(nums) {
  if (nums.length <= 1) return nums.length;
  let write = 0;
  for (let read = 1; read < nums.length; read++) {
    if (nums[read] !== nums[write]) nums[++write] = nums[read];
  }
  return write + 1;
}

Prefix Sum + HashMap

Count subarrays meeting a sum property, often O(n).

function subarraySumEqualsK(nums, k) {
  const count = new Map([[0, 1]]);
  let prefix = 0,
    ans = 0;
  for (const x of nums) {
    prefix += x;
    ans += count.get(prefix - k) ?? 0;
    count.set(prefix, (count.get(prefix) ?? 0) + 1);
  }
  return ans;
}

Monotonic Stack / Deque

Next greater element (strictly decreasing stack of indices)

function nextGreaterElements(nums) {
  const n = nums.length,
    res = Array(n).fill(-1),
    st = [];
  for (let i = 0; i < n; i++) {
    while (st.length && nums[i] > nums[st.at(-1)]) res[st.pop()] = i;
    st.push(i);
  }
  return res; // indices (or store values per need)
}

Heaps (Priority Queues)

Top-K, running median, merge K lists.

// Use a library PQ in practice; this is the strategy:
// - Top-K frequent: count with Map -> push into max-heap by freq -> pop K.
// - Kth largest stream: maintain min-heap of size K.

Intervals (Sort + Greedy / Heap)

function mergeIntervals(intervals) {
  intervals.sort((a, b) => a[0] - b[0]);
  const res = [];
  for (const [s, e] of intervals) {
    if (!res.length || res.at(-1)[1] < s) res.push([s, e]);
    else res.at(-1)[1] = Math.max(res.at(-1)[1], e);
  }
  return res;
}

Meeting rooms (min rooms) → sort by start; min-heap by end; pop while earliest end ≤ start.


Binary Search (value or answer)

Classic BS on index; or BS on answer with feasibility check.

function binarySearch(nums, target) {
  let l = 0,
    r = nums.length - 1;
  while (l <= r) {
    const m = l + ((r - l) >> 1);
    if (nums[m] === target) return m;
    if (nums[m] < target) l = m + 1;
    else r = m - 1;
  }
  return -1;
}

function canShip(weights, days, cap) {
  let need = 1,
    cur = 0;
  for (const w of weights) {
    if (w > cap) return false;
    if (cur + w > cap) {
      need++;
      cur = 0;
    }
    cur += w;
  }
  return need <= days;
}

function shipWithinDays(weights, days) {
  let l = Math.max(...weights),
    r = weights.reduce((a, b) => a + b, 0),
    ans = r;
  while (l <= r) {
    const m = l + ((r - l) >> 1);
    if (canShip(weights, days, m)) {
      ans = m;
      r = m - 1;
    } else {
      l = m + 1;
    }
  }
  return ans;
}

Graphs

BFS shortest path (unweighted)

function shortestPathUndirected(n, edges, src) {
  const g = Array.from({ length: n }, () => []);
  for (const [u, v] of edges) (g[u].push(v), g[v].push(u));
  const dist = Array(n).fill(Infinity);
  dist[src] = 0;
  const q = [src];
  for (let qi = 0; qi < q.length; qi++) {
    const u = q[qi];
    for (const v of g[u])
      if (dist[v] === Infinity) {
        dist[v] = dist[u] + 1;
        q.push(v);
      }
  }
  return dist;
}

Topological sort (DAG) + DP

function topoOrder(n, edges) {
  const g = Array.from({ length: n }, () => []),
    indeg = Array(n).fill(0);
  for (const [u, v] of edges) (g[u].push(v), indeg[v]++);
  const q = [],
    order = [];
  for (let i = 0; i < n; i++) if (indeg[i] === 0) q.push(i);
  for (let qi = 0; qi < q.length; qi++) {
    const u = q[qi];
    order.push(u);
    for (const v of g[u]) if (--indeg[v] === 0) q.push(v);
  }
  return order.length === n ? order : [];
}

Trees

Iterative inorder (BST operations), BFS level order, DFS variants.

function inorderTraversal(root) {
  const st = [],
    res = [];
  let cur = root;
  while (cur || st.length) {
    while (cur) {
      st.push(cur);
      cur = cur.left;
    }
    cur = st.pop();
    res.push(cur.val);
    cur = cur.right;
  }
  return res;
}

Lowest Common Ancestor in BST uses BST property; in general tree use parent pointers.


Linked List (Fast/Slow)

function hasCycle(head) {
  let slow = head,
    fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) return true;
  }
  return false;
}

Union-Find (Disjoint Set Union)

class DSU {
  constructor(n) {
    this.p = Array.from({ length: n }, (_, i) => i);
    this.r = Array(n).fill(0);
    this.c = n;
  }
  find(x) {
    return this.p[x] === x ? x : (this.p[x] = this.find(this.p[x]));
  }
  union(a, b) {
    a = this.find(a);
    b = this.find(b);
    if (a === b) return false;
    if (this.r[a] < this.r[b]) [a, b] = [b, a];
    this.p[b] = a;
    if (this.r[a] === this.r[b]) this.r[a]++;
    this.c--;
    return true;
  }
}

Dynamic Programming

0/1 Knapsack (capacity-based DP)

function knapsack(weights, values, W) {
  const dp = Array(W + 1).fill(0);
  for (let i = 0; i < weights.length; i++) {
    for (let w = W; w >= weights[i]; w--) {
      dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
    }
  }
  return dp[W];
}

Edit Distance (2D DP)

function editDistance(a, b) {
  const m = a.length,
    n = b.length;
  const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
  for (let i = 0; i <= m; i++) dp[i][0] = i;
  for (let j = 0; j <= n; j++) dp[0][j] = j;
  for (let i = 1; i <= m; i++)
    for (let j = 1; j <= n; j++)
      dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
  return dp[m][n];
}

Backtracking

Subsets, permutations, combinations, restore IP.

function subsets(nums) {
  const res = [],
    path = [];
  (function dfs(i) {
    if (i === nums.length) {
      res.push(path.slice());
      return;
    }
    dfs(i + 1);
    path.push(nums[i]);
    dfs(i + 1);
    path.pop();
  })(0);
  return res;
}

Greedy

Prove by exchange argument or invariant.

function canJump(nums) {
  let far = 0;
  for (let i = 0; i < nums.length; i++) {
    if (i > far) return false;
    far = Math.max(far, i + nums[i]);
  }
  return true;
}

Bit Tricks

function singleNumber(nums) {
  return nums.reduce((a, b) => a ^ b, 0);
}

Pattern Recognition Checklist

  1. Is it contiguous and constraint-based? → Window
  2. Sorted or can be sorted to use structure? → Two pointers / Intervals / Greedy
  3. Counting subarrays with exact property? → Prefix + HashMap
  4. Next greater/smaller or window extremes? → Monotonic stack/deque
  5. Kth/Top-K/streaming? → Heap
  6. “Can we achieve X with Y?” → BS on answer + feasibility
  7. Graph-ish wording (shortest path, prereqs, components)? → BFS/DFS/Topo/DSU
  8. Overlapping subproblems/states? → DP
  9. Enumerate all valid configurations? → Backtracking
  10. Bits/parity/XOR hints? → Bit ops

That’s it. Spot the trigger, pick the pattern, paste the template, and finish the condition.

Back to Blog