Kazi Rahamatullah
Kazi Rahamatullah
AboutProjectsBlogContact
UsesBooks
ResumeView CV
Kazi Rahamatullah

© Copyright 2026 Kazi Rahamatullah

AboutProjectsBlogBooksUses
Twitter/XGitHubProduct HuntCodeSandbox
Back to Blog
DSAInterviewPatternsAlgorithms

90 Valuable Interview Coding Problems — 20 Patterns Master Sheet

90 essential interview coding problems organized by 20 patterns — step-by-step approach, JavaScript solution, hand-written trace, and demo calls with output.

Jul 7, 202682 min read

Introduction

Pattern recognition is the fastest path through coding interviews. This master sheet covers 90 curated problems across 20 patterns, each with:

  1. Step-by-step approach
  2. Full JavaScript implementation
  3. Hand-written execution trace
  4. Function calls with side output comments

Prerequisites: Big O notation · DSA Arrays

Quick index

Pattern 1 — Arrays

#ProblemLevelTime
1Move ZeroesEasyO(n)
2Product of Array Except SelfMediumO(n)
3Rotate ArrayMediumO(n)
4Merge IntervalsMediumO(n log n)
5Insert IntervalMediumO(n)
6First Missing PositiveHardO(n)
7Find Duplicate Number (LC 287)MediumO(n)
8Find All Numbers Disappeared (LC 448)EasyO(n)
9Set Mismatch (LC 645)EasyO(n)
10Find All Duplicates (LC 442)MediumO(n)
11Maximum Subarray SumMediumO(n)
12Best Time to Buy and Sell StockEasyO(n)

Pattern 2 — Two Pointers

#ProblemLevelTime
13Two Sum IIMediumO(n)
14Container With Most WaterMediumO(n)
153SumMediumO(n²)
16Remove Duplicates from Sorted ArrayEasyO(n)
17Valid PalindromeEasyO(n)

Pattern 3 — Sliding Window

#ProblemLevelTime
18Longest Substring Without Repeating CharactersMediumO(n)
19Minimum Window SubstringHardO(n)
20Permutation in StringMediumO(n)
21Find All Anagrams in a StringMediumO(n)
22Maximum Average Subarray IEasyO(n)

Pattern 4 — Prefix Sum

#ProblemLevelTime
23Subarray Sum Equals KMediumO(n)
24Continuous Subarray SumMediumO(n)
25Range Sum QueryEasyO(n) build, O(1) query
26Product of Array Except Self (Prefix)MediumO(n)

Pattern 5 — Hash Map

#ProblemLevelTime
27Two SumEasyO(n)
28Group AnagramsMediumO(n · k log k)
29Isomorphic StringsEasyO(n)
30Happy NumberEasyO(log n)

Pattern 6 — Fast Slow Pointers

#ProblemLevelTime
31Linked List CycleEasyO(n)
32Linked List Cycle IIMediumO(n)
33Happy Number (Floyd)EasyO(log n)
34Find Duplicate Number (Floyd)MediumO(n)

Pattern 7 — Linked List

#ProblemLevelTime
35Reverse Linked ListEasyO(n)
36Merge Two Sorted ListsEasyO(n + m)
37Remove Nth Node From EndMediumO(n)
38Add Two NumbersMediumO(max(n,m))
39Copy List With Random PointerMediumO(n)
40Swap Nodes in PairsMediumO(n)
41Rotate ListMediumO(n)

Pattern 8 — Binary Search

#ProblemLevelTime
42Binary SearchEasyO(log n)
43Search in Rotated Sorted ArrayMediumO(log n)
44Find Peak ElementMediumO(log n)
45Median of Two Sorted ArraysHardO(log min(n,m))

Pattern 9 — Stack

#ProblemLevelTime
46Valid ParenthesesEasyO(n)
47Daily TemperaturesMediumO(n)
48Next Greater ElementMediumO(n)
49Largest Rectangle in HistogramHardO(n)
50132 PatternMediumO(n)

Pattern 10 — Heap

#ProblemLevelTime
51Top K Frequent ElementsMediumO(n log k)
52K Closest Points to OriginMediumO(n log k)
53Find Median from Data StreamHardO(log n) per add
54Merge K Sorted ListsHardO(n log k)

Pattern 11 — Backtracking

#ProblemLevelTime
55SubsetsMediumO(n · 2^n)
56PermutationsMediumO(n · n!)
57Combination SumMediumO(2^n)
58Word SearchMediumO(m · n · 4^L)
59N-QueensHardO(n!)

Pattern 12 — Trees DFS

#ProblemLevelTime
60Maximum Depth of Binary TreeEasyO(n)
61Path SumEasyO(n)
62Diameter of Binary TreeEasyO(n)
63Lowest Common AncestorMediumO(n)

Pattern 13 — Trees BFS

#ProblemLevelTime
64Binary Tree Level Order TraversalMediumO(n)
65Binary Tree Right Side ViewMediumO(n)
66Minimum Depth of Binary TreeEasyO(n)

Pattern 14 — BST

#ProblemLevelTime
67Validate BSTMediumO(n)
68Kth Smallest Element in BSTMediumO(h + k)
69Convert Sorted Array to BSTEasyO(n)

Pattern 15 — Trie

#ProblemLevelTime
70Implement TrieMediumO(m) per op
71Word Search IIHardO(m · n · 4^L)
72Design Add and Search WordsMediumO(m) add, O(26^dots) search

Pattern 16 — Graphs

#ProblemLevelTime
73Number of IslandsMediumO(m · n)
74Clone GraphMediumO(V + E)
75Course ScheduleMediumO(V + E)
76Pacific Atlantic Water FlowMediumO(m · n)

Pattern 17 — Union Find

#ProblemLevelTime
77Number of Connected ComponentsMediumO(n + e · α(n))
78Redundant ConnectionMediumO(n · α(n))
79Accounts MergeMediumO(n · k · α(n))

Pattern 18 — DP

#ProblemLevelTime
80Climbing StairsEasyO(n)
81House RobberMediumO(n)
82Longest Increasing SubsequenceMediumO(n log n)
83Coin ChangeMediumO(n · amount)
84Edit DistanceMediumO(m · n)

Pattern 19 — Greedy

#ProblemLevelTime
85Jump GameMediumO(n)
86Gas StationMediumO(n)
87Partition LabelsMediumO(n)

Pattern 20 — Bit Manipulation

#ProblemLevelTime
88Single NumberEasyO(n)
89Counting BitsEasyO(n)
90Reverse BitsEasyO(1)

Pattern 1 — Arrays

1. Move Zeroes

Difficulty: Easy · Time: O(n) · Space: O(1)

Move all zeroes to the end while preserving relative order of non-zero elements in-place.

Approach (step by step):

  1. Maintain write pointer for next non-zero slot.
  2. Scan: copy non-zero values forward.
  3. Fill remaining indices with zero.
js
function moveZeroes(arr) {
  let w = 0
  for (const x of arr) if (x !== 0) arr[w++] = x
  while (w < arr.length) arr[w++] = 0
  return arr
}

Hand-written trace

js
// moveZeroes([0,1,0,3,12])
// i=0  val=0  write=0  → skip
// i=1  val=1  arr[0]=1  write=1
// i=2  val=0  write=1  → skip
// i=3  val=3  arr[1]=3  write=2
// i=4  val=12 arr[2]=12 write=3
// fill arr[3..4]=0
// result → [1,3,12,0,0]

Function calls with output

js
// ── Demo calls — output shown on the right ──
moveZeroes([0, 1, 0, 3, 12]) // → [1, 3, 12, 0, 0]
moveZeroes([0, 0, 1]) // → [1, 0, 0]

Back to index


2. Product of Array Except Self

Difficulty: Medium · Time: O(n) · Space: O(1)

Return array where output[i] is product of all elements except arr[i] without division.

Approach (step by step):

  1. First pass: store prefix product at each index.
  2. Second pass: multiply by suffix running product right-to-left.
js
function productExceptSelf(arr) {
  const n = arr.length
  const out = Array(n).fill(1)
  let prefix = 1
  for (let i = 0; i < n; i++) {
    out[i] = prefix
    prefix *= arr[i]
  }
  let suffix = 1
  for (let i = n - 1; i >= 0; i--) {
    out[i] *= suffix
    suffix *= arr[i]
  }
  return out
}

Hand-written trace

js
// productExceptSelf([1,2,3,4])
// prefix pass: out=[1,1,2,6] prefix ends 24
// suffix i=3: out[3]=6*1=6 suffix=4
// suffix i=2: out[2]=2*4=8 suffix=12
// suffix i=1: out[1]=1*12=12 suffix=24
// suffix i=0: out[0]=1*24=24
// result → [24,12,8,6]

Function calls with output

js
// ── Demo calls — output shown on the right ──
productExceptSelf([1, 2, 3, 4]) // → [24, 12, 8, 6]
productExceptSelf([2, 3, 4, 5]) // → [60, 40, 30, 24]

Back to index


3. Rotate Array

Difficulty: Medium · Time: O(n) · Space: O(1)

Rotate array to the right by k steps in-place.

Approach (step by step):

  1. Normalize k %= n.
  2. Reverse whole array, then reverse first k and last n-k parts.
js
function rotateArray(arr, k) {
  const n = arr.length
  if (!n) return arr
  k %= n
  const rev = (l, r) => {
    while (l < r) {
      ;[arr[l], arr[r]] = [arr[r], arr[l]]
      l++
      r--
    }
  }
  rev(0, n - 1)
  rev(0, k - 1)
  rev(k, n - 1)
  return arr
}

Hand-written trace

js
// rotateArray([1,2,3,4,5], 2)  k=2
// reverse all → [5,4,3,2,1]
// reverse [0..1] → [4,5,3,2,1]
// reverse [2..4] → [4,5,1,2,3]
// result → [4,5,1,2,3]

Function calls with output

js
// ── Demo calls — output shown on the right ──
rotateArray([1, 2, 3, 4, 5], 2) // → [4, 5, 1, 2, 3]
rotateArray([1, 2], 3) // → [2, 1]

Back to index


4. Merge Intervals

Difficulty: Medium · Time: O(n log n) · Space: O(n)

Merge all overlapping intervals and return non-overlapping list.

Approach (step by step):

  1. Sort by start.
  2. If current overlaps last, extend end; else push new interval.
js
function mergeIntervals(intervals) {
  if (!intervals.length) return []
  intervals.sort((a, b) => a[0] - b[0])
  const out = [intervals[0].slice()]
  for (let i = 1; i < intervals.length; i++) {
    const last = out[out.length - 1]
    if (intervals[i][0] <= last[1]) last[1] = Math.max(last[1], intervals[i][1])
    else out.push(intervals[i].slice())
  }
  return out
}

Hand-written trace

js
// mergeIntervals([[1,3],[2,6],[8,10],[15,18]])
// sort unchanged
// out=[[1,3]] → merge [2,6] → [[1,6]]
// [8,10] new → [[1,6],[8,10]]
// [15,18] new → [[1,6],[8,10],[15,18]]

Function calls with output

js
// ── Demo calls — output shown on the right ──
mergeIntervals([
  [1, 3],
  [2, 6],
  [8, 10],
  [15, 18],
]) // → [[1,6],[8,10],[15,18]]
mergeIntervals([
  [1, 4],
  [4, 5],
]) // → [[1,5]]

Back to index


5. Insert Interval

Difficulty: Medium · Time: O(n) · Space: O(n)

Insert new interval into sorted non-overlapping list, merging if needed.

Approach (step by step):

  1. Add intervals ending before new.
  2. Merge overlapping.
  3. Append rest.
js
function insertInterval(intervals, ni) {
  const out = []
  let i = 0,
    n = intervals.length
  while (i < n && intervals[i][1] < ni[0]) out.push(intervals[i++])
  while (i < n && intervals[i][0] <= ni[1]) {
    ni[0] = Math.min(ni[0], intervals[i][0])
    ni[1] = Math.max(ni[1], intervals[i][1])
    i++
  }
  out.push(ni)
  while (i < n) out.push(intervals[i++])
  return out
}

Hand-written trace

js
// insertInterval([[1,3],[6,9]], [2,5])
// [1,3] overlaps → merged [1,5]
// [6,9] after → [[1,5],[6,9]]

Function calls with output

js
// ── Demo calls — output shown on the right ──
insertInterval(
  [
    [1, 3],
    [6, 9],
  ],
  [2, 5],
) // → [[1,5],[6,9]]
insertInterval(
  [
    [1, 2],
    [3, 5],
    [6, 7],
    [8, 10],
    [12, 16],
  ],
  [4, 8],
) // → [[1,2],[3,10],[12,16]]

Back to index


6. First Missing Positive

Difficulty: Hard · Time: O(n) · Space: O(1)

Find smallest missing positive integer in unsorted array.

Approach (step by step):

  1. Cyclic sort: place value v at index v-1 when 1 ≤ v ≤ n.
  2. Scan for first index where arr[i] ≠ i+1.
js
function firstMissingPositive(arr) {
  const n = arr.length
  for (let i = 0; i < n; i++) {
    while (arr[i] > 0 && arr[i] <= n && arr[arr[i] - 1] !== arr[i]) {
      ;[arr[arr[i] - 1], arr[i]] = [arr[i], arr[arr[i] - 1]]
    }
  }
  for (let i = 0; i < n; i++) if (arr[i] !== i + 1) return i + 1
  return n + 1
}

Hand-written trace

js
// firstMissingPositive([3,4,-1,1])
// place 1→idx0, 3→idx2, 4→idx3 → [1,-1,3,4]
// index1 arr[1]=-1 ≠ 2 → return 2

Function calls with output

js
// ── Demo calls — output shown on the right ──
firstMissingPositive([3, 4, -1, 1]) // → 2
firstMissingPositive([1, 2, 0]) // → 3

Back to index


7. Find Duplicate Number (LC 287)

Difficulty: Medium · Time: O(n) · Space: O(1)

Array of n+1 integers in [1,n]; exactly one duplicate. Find it.

Approach (step by step):

  1. Treat as linked list cycle: index → arr[index].
  2. Floyd cycle detection; duplicate is cycle entry.
js
function findDuplicate(arr) {
  let slow = arr[0],
    fast = arr[0]
  do {
    slow = arr[slow]
    fast = arr[arr[fast]]
  } while (slow !== fast)
  slow = arr[0]
  while (slow !== fast) {
    slow = arr[slow]
    fast = arr[fast]
  }
  return slow
}

Hand-written trace

js
// findDuplicate([1,3,4,2,2])
// slow:1→3→2  fast:1→4→2→2 meet at 2
// reset slow=1, advance both → meet at 2
// result → 2

Function calls with output

js
// ── Demo calls — output shown on the right ──
findDuplicate([1, 3, 4, 2, 2]) // → 2
findDuplicate([3, 1, 3, 4, 2]) // → 3

Back to index


8. Find All Numbers Disappeared (LC 448)

Difficulty: Easy · Time: O(n) · Space: O(1)

Return all numbers in [1,n] missing from array of length n.

Approach (step by step):

  1. Mark visited index abs(arr[i])-1 by negating.
  2. Collect indices still positive +1.
js
function findDisappearedNumbers(arr) {
  const n = arr.length
  for (let i = 0; i < n; i++) {
    const idx = Math.abs(arr[i]) - 1
    if (arr[idx] > 0) arr[idx] = -arr[idx]
  }
  const out = []
  for (let i = 0; i < n; i++) if (arr[i] > 0) out.push(i + 1)
  return out
}

Hand-written trace

js
// findDisappearedNumbers([4,3,2,7,8,2,3,1])
// negate at indices 3,2,1,6,7,1,2,0
// positive indices 4,5 → missing 5,6

Function calls with output

js
// ── Demo calls — output shown on the right ──
findDisappearedNumbers([4, 3, 2, 7, 8, 2, 3, 1]) // → [5, 6]
findDisappearedNumbers([1, 1]) // → [2]

Back to index


9. Set Mismatch (LC 645)

Difficulty: Easy · Time: O(n) · Space: O(1)

Array represents set with one duplicate and one missing. Return [duplicate, missing].

Approach (step by step):

  1. XOR/index marking or math on sum and sum of squares.
js
function findErrorNums(arr) {
  const n = arr.length
  let sum = 0,
    sq = 0
  for (const x of arr) {
    sum += x
    sq += x * x
  }
  const tSum = (n * (n + 1)) / 2
  const tSq = (n * (n + 1) * (2 * n + 1)) / 6
  const d = sum - tSum
  const dSq = sq - tSq
  const dup = (dSq / d + d) / 2
  const missing = dup - d
  return [dup, missing]
}

Hand-written trace

js
// findErrorNums([1,2,2,4])
// sum=9 tSum=10 d=-1  sq=25 tSq=30 dSq=-5
// dup=2 missing=3 → [2,3]

Function calls with output

js
// ── Demo calls — output shown on the right ──
findErrorNums([1, 2, 2, 4]) // → [2, 3]
findErrorNums([1, 1]) // → [1, 2]

Back to index


10. Find All Duplicates (LC 442)

Difficulty: Medium · Time: O(n) · Space: O(1)

Find all elements appearing twice in array where 1 ≤ arr[i] ≤ n.

Approach (step by step):

  1. Same index marking: if arr[idx] already negative, idx+1 is duplicate.
js
function findDuplicates(arr) {
  const out = []
  for (let i = 0; i < arr.length; i++) {
    const idx = Math.abs(arr[i]) - 1
    if (arr[idx] < 0) out.push(idx + 1)
    else arr[idx] = -arr[idx]
  }
  return out
}

Hand-written trace

js
// findDuplicates([4,3,2,7,8,2,3,1])
// second visit to idx1 → 2, idx2 → 3
// result → [2,3]

Function calls with output

js
// ── Demo calls — output shown on the right ──
findDuplicates([4, 3, 2, 7, 8, 2, 3, 1]) // → [2, 3]
findDuplicates([1, 1, 2]) // → [1]

Back to index


11. Maximum Subarray Sum

Difficulty: Medium · Time: O(n) · Space: O(1)

Find contiguous subarray with largest sum (Kadane).

Approach (step by step):

  1. Extend running sum or restart at current element.
  2. Track global maximum.
js
function maxSubarraySum(arr) {
  let cur = arr[0],
    best = arr[0]
  for (let i = 1; i < arr.length; i++) {
    cur = Math.max(arr[i], cur + arr[i])
    best = Math.max(best, cur)
  }
  return best
}

Hand-written trace

js
// maxSubarraySum([-2,1,-3,4,-1,2,1,-5,4])
// cur: -2,1,-2,4,3,5,6,1,5  best max=6 at [4,-1,2,1]

Function calls with output

js
// ── Demo calls — output shown on the right ──
maxSubarraySum([-2, 1, -3, 4, -1, 2, 1, -5, 4]) // → 6
maxSubarraySum([5, 4, -1, 7, 8]) // → 23

Back to index


12. Best Time to Buy and Sell Stock

Difficulty: Easy · Time: O(n) · Space: O(1)

Max profit from one buy and one sell.

Approach (step by step):

  1. Track minimum price so far.
  2. Update max profit at each day.
js
function maxProfit(prices) {
  let minP = Infinity,
    best = 0
  for (const p of prices) {
    minP = Math.min(minP, p)
    best = Math.max(best, p - minP)
  }
  return best
}

Hand-written trace

js
// maxProfit([7,1,5,3,6,4])
// minP:7,1,1,1,1  profit:0,4,2,5
// buy@1 sell@6 → 5

Function calls with output

js
// ── Demo calls — output shown on the right ──
maxProfit([7, 1, 5, 3, 6, 4]) // → 5
maxProfit([7, 6, 4, 3, 1]) // → 0

Back to index


Pattern 2 — Two Pointers

13. Two Sum II

Difficulty: Medium · Time: O(n) · Space: O(1)

Sorted array; return 1-indexed pair summing to target.

Approach (step by step):

  1. Left at start, right at end.
  2. If sum too small move left++; too big move right--.
js
function twoSumII(arr, target) {
  let l = 0,
    r = arr.length - 1
  while (l < r) {
    const s = arr[l] + arr[r]
    if (s === target) return [l + 1, r + 1]
    if (s < target) l++
    else r--
  }
  return []
}

Hand-written trace

js
// twoSumII([2,7,11,15], 9)
// l=0,r=3 sum=17>9 r--
// l=0,r=2 sum=13>9 r--
// l=0,r=1 sum=9 → [1,2]

Function calls with output

js
// ── Demo calls — output shown on the right ──
twoSumII([2, 7, 11, 15], 9) // → [1, 2]
twoSumII([2, 3, 4], 6) // → [1, 3]

Back to index


14. Container With Most Water

Difficulty: Medium · Time: O(n) · Space: O(1)

Max area between two vertical lines.

Approach (step by step):

  1. Two pointers at ends.
  2. Move shorter side inward; track max area.
js
function maxArea(arr) {
  let l = 0,
    r = arr.length - 1,
    best = 0
  while (l < r) {
    best = Math.max(best, Math.min(arr[l], arr[r]) * (r - l))
    if (arr[l] < arr[r]) l++
    else r--
  }
  return best
}

Hand-written trace

js
// maxArea([1,8,6,2,5,4,8,3,7])
// best at l=1,r=8: min(8,7)*7=49

Function calls with output

js
// ── Demo calls — output shown on the right ──
maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7]) // → 49
maxArea([1, 1]) // → 1

Back to index


15. 3Sum

Difficulty: Medium · Time: O(n²) · Space: O(1)

Return all unique triplets summing to zero.

Approach (step by step):

  1. Sort array.
  2. Fix i; two-pointer scan for pairs with sum -arr[i].
  3. Skip duplicates.
js
function threeSum(arr) {
  arr.sort((a, b) => a - b)
  const out = []
  for (let i = 0; i < arr.length - 2; i++) {
    if (i && arr[i] === arr[i - 1]) continue
    let l = i + 1,
      r = arr.length - 1
    while (l < r) {
      const s = arr[i] + arr[l] + arr[r]
      if (s === 0) {
        out.push([arr[i], arr[l], arr[r]])
        while (l < r && arr[l] === arr[l + 1]) l++
        while (l < r && arr[r] === arr[r - 1]) r--
        l++
        r--
      } else if (s < 0) l++
      else r--
    }
  }
  return out
}

Hand-written trace

js
// threeSum([-1,0,1,2,-1,-4]) sorted [-4,-1,-1,0,1,2]
// i=-4: no zero sum
// i=-1: pairs → [-1,-1,2], [-1,0,1]

Function calls with output

js
// ── Demo calls — output shown on the right ──
threeSum([-1, 0, 1, 2, -1, -4]) // → [[-1,-1,2],[-1,0,1]]
threeSum([0, 0, 0]) // → [[0,0,0]]

Back to index


16. Remove Duplicates from Sorted Array

Difficulty: Easy · Time: O(n) · Space: O(1)

Remove duplicates in-place; return count of unique elements.

Approach (step by step):

  1. Slow pointer marks last unique; fast scans and copies new values.
js
function removeDuplicates(arr) {
  if (!arr.length) return 0
  let w = 1
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] !== arr[w - 1]) arr[w++] = arr[i]
  }
  return w
}

Hand-written trace

js
// removeDuplicates([1,1,2,2,3])
// w=1: copy 2→w=2, copy 3→w=3
// return 3

Function calls with output

js
// ── Demo calls — output shown on the right ──
removeDuplicates([1, 1, 2, 2, 3]) // → 3  (array starts [1,2,3])
removeDuplicates([1, 2, 3]) // → 3

Back to index


17. Valid Palindrome

Difficulty: Easy · Time: O(n) · Space: O(1)

Check if string is palindrome considering alphanumeric only, case-insensitive.

Approach (step by step):

  1. Two pointers skip non-alnum.
  2. Compare lowercased chars; move inward.
js
function isPalindrome(s) {
  const isAlnum = (c) => /[a-z0-9]/i.test(c)
  let l = 0,
    r = s.length - 1
  while (l < r) {
    while (l < r && !isAlnum(s[l])) l++
    while (l < r && !isAlnum(s[r])) r--
    if (s[l].toLowerCase() !== s[r].toLowerCase()) return false
    l++
    r--
  }
  return true
}

Hand-written trace

js
// isPalindrome("A man, a plan, a canal: Panama")
// compare a↔a, m↔m, ... all match → true

Function calls with output

js
// ── Demo calls — output shown on the right ──
isPalindrome('A man, a plan, a canal: Panama') // → true
isPalindrome('race a car') // → false

Back to index


Pattern 3 — Sliding Window

18. Longest Substring Without Repeating Characters

Difficulty: Medium · Time: O(n) · Space: O(min(n,σ))

Length of longest substring without repeating characters.

Approach (step by step):

  1. Expand right; shrink left while duplicate in window.
  2. Track char last index and max length.
js
function lengthOfLongestSubstring(s) {
  const last = new Map()
  let l = 0,
    best = 0
  for (let r = 0; r < s.length; r++) {
    if (last.has(s[r]) && last.get(s[r]) >= l) l = last.get(s[r]) + 1
    last.set(s[r], r)
    best = Math.max(best, r - l + 1)
  }
  return best
}

Hand-written trace

js
// lengthOfLongestSubstring("abcabcbb")
// window "abc" len=3 best; shrink at repeat b,c
// best stays 3

Function calls with output

js
// ── Demo calls — output shown on the right ──
lengthOfLongestSubstring('abcabcbb') // → 3
lengthOfLongestSubstring('bbbbb') // → 1

Back to index


19. Minimum Window Substring

Difficulty: Hard · Time: O(n) · Space: O(1)

Smallest substring of s containing all characters of t.

Approach (step by step):

  1. Expand until valid; shrink from left while valid.
  2. Track need/have counts.
js
function minWindow(s, t) {
  if (!t.length) return ''
  const need = new Map()
  for (const c of t) need.set(c, (need.get(c) ?? 0) + 1)
  let have = 0,
    goal = need.size,
    l = 0,
    best = '',
    bestLen = Infinity
  const win = new Map()
  for (let r = 0; r < s.length; r++) {
    const c = s[r]
    win.set(c, (win.get(c) ?? 0) + 1)
    if (need.has(c) && win.get(c) === need.get(c)) have++
    while (have === goal) {
      if (r - l + 1 < bestLen) {
        bestLen = r - l + 1
        best = s.slice(l, r + 1)
      }
      const lc = s[l++]
      if (need.has(lc) && win.get(lc) === need.get(lc)) have--
      win.set(lc, win.get(lc) - 1)
    }
  }
  return best
}

Hand-written trace

js
// minWindow("ADOBECODEBANC","ABC")
// valid at ADOBEC, shrink → BANC len=4 best

Function calls with output

js
// ── Demo calls — output shown on the right ──
minWindow('ADOBECODEBANC', 'ABC') // → "BANC"
minWindow('a', 'a') // → "a"

Back to index


20. Permutation in String

Difficulty: Medium · Time: O(n) · Space: O(1)

Return true if s2 contains permutation of s1.

Approach (step by step):

  1. Fixed window size |s1|.
  2. Compare frequency maps; slide one char at a time.
js
function checkInclusion(s1, s2) {
  if (s1.length > s2.length) return false
  const cnt = (s) => {
    const m = Array(26).fill(0)
    for (const c of s) m[c.charCodeAt(0) - 97]++
    return m
  }
  const a = cnt(s1),
    b = cnt(s2.slice(0, s1.length))
  const eq = () => a.every((v, i) => v === b[i])
  if (eq()) return true
  for (let i = s1.length; i < s2.length; i++) {
    b[s2.charCodeAt(i - s1.length) - 97]--
    b[s2.charCodeAt(i) - 97]++
    if (eq()) return true
  }
  return false
}

Hand-written trace

js
// checkInclusion("ab","eidbaooo")
// window "dba" matches ab permutation → true

Function calls with output

js
// ── Demo calls — output shown on the right ──
checkInclusion('ab', 'eidbaooo') // → true
checkInclusion('ab', 'eidboaoo') // → false

Back to index


21. Find All Anagrams in a String

Difficulty: Medium · Time: O(n) · Space: O(1)

Return start indices of all anagrams of p in s.

Approach (step by step):

  1. Sliding window of len(p) with freq diff counter.
  2. When diff zero, record start index.
js
function findAnagrams(s, p) {
  if (p.length > s.length) return []
  const diff = Array(26).fill(0)
  for (let i = 0; i < p.length; i++) {
    diff[p.charCodeAt(i) - 97]++
    diff[s.charCodeAt(i) - 97]--
  }
  const out = []
  const ok = () => diff.every((x) => x === 0)
  if (ok()) out.push(0)
  for (let i = p.length; i < s.length; i++) {
    diff[s.charCodeAt(i - p.length) - 97]++
    diff[s.charCodeAt(i) - 97]--
    if (ok()) out.push(i - p.length + 1)
  }
  return out
}

Hand-written trace

js
// findAnagrams("cbaebabacd","abc")
// index0 "cba", index6 "bac" → [0,6]

Function calls with output

js
// ── Demo calls — output shown on the right ──
findAnagrams('cbaebabacd', 'abc') // → [0, 6]
findAnagrams('abab', 'ab') // → [0, 1, 2]

Back to index


22. Maximum Average Subarray I

Difficulty: Easy · Time: O(n) · Space: O(1)

Max average of any contiguous subarray of length k.

Approach (step by step):

  1. Sum first k elements.
  2. Slide: add next, subtract leaving; track max sum.
js
function findMaxAverage(arr, k) {
  let sum = 0
  for (let i = 0; i < k; i++) sum += arr[i]
  let best = sum
  for (let i = k; i < arr.length; i++) {
    sum += arr[i] - arr[i - k]
    best = Math.max(best, sum)
  }
  return best / k
}

Hand-written trace

js
// findMaxAverage([1,12,-5,-6,50,3], 4)
// sums: 2, 51 best → 51/4=12.75

Function calls with output

js
// ── Demo calls — output shown on the right ──
findMaxAverage([1, 12, -5, -6, 50, 3], 4) // → 12.75
findMaxAverage([5], 1) // → 5

Back to index


Pattern 4 — Prefix Sum

23. Subarray Sum Equals K

Difficulty: Medium · Time: O(n) · Space: O(n)

Count subarrays with sum exactly k.

Approach (step by step):

  1. Prefix sum + hash map of prefix frequency.
  2. Add count of prefix sum - k at each step.
js
function subarraySum(arr, k) {
  const freq = new Map([[0, 1]])
  let sum = 0,
    count = 0
  for (const x of arr) {
    sum += x
    count += freq.get(sum - k) ?? 0
    freq.set(sum, (freq.get(sum) ?? 0) + 1)
  }
  return count
}

Hand-written trace

js
// subarraySum([1,1,1], 2)
// prefix 1→0, 2→1 hit, 3→1 hit → 2

Function calls with output

js
// ── Demo calls — output shown on the right ──
subarraySum([1, 1, 1], 2) // → 2
subarraySum([1, 2, 3], 3) // → 2

Back to index


24. Continuous Subarray Sum

Difficulty: Medium · Time: O(n) · Space: O(n)

True if array has subarray len ≥ 2 with sum multiple of k.

Approach (step by step):

  1. Store prefix mod k in map with index.
  2. If same mod seen ≥2 apart, return true.
js
function checkSubarraySum(arr, k) {
  const modIdx = new Map([[0, -1]])
  let sum = 0
  for (let i = 0; i < arr.length; i++) {
    sum += arr[i]
    const mod = k === 0 ? sum : sum % k
    if (modIdx.has(mod)) {
      if (i - modIdx.get(mod) >= 2) return true
    } else modIdx.set(mod, i)
  }
  return false
}

Hand-written trace

js
// checkSubarraySum([23,2,4,6,7], 6)
// mod 2 at idx1 and idx4 len≥2 → true

Function calls with output

js
// ── Demo calls — output shown on the right ──
checkSubarraySum([23, 2, 4, 6, 7], 6) // → true
checkSubarraySum([23, 2, 6, 4, 7], 6) // → true

Back to index


25. Range Sum Query

Difficulty: Easy · Time: O(n) build, O(1) query · Space: O(n)

Immutable array: answer sum queries sumRange(left, right) in O(1).

Approach (step by step):

  1. Build prefix array where prefix[i] = sum of nums[0..i-1].
  2. Query = prefix[right+1] - prefix[left].
js
class NumArray {
  constructor(nums) {
    this.prefix = [0]
    for (const x of nums) this.prefix.push(this.prefix.at(-1) + x)
  }
  sumRange(l, r) {
    return this.prefix[r + 1] - this.prefix[l]
  }
}

Hand-written trace

js
// NumArray([-2,0,3,-4,2,-1])
// prefix [0,-2,-2,1,-3,-1,-2]
// sumRange(0,2) = prefix[3]-prefix[0] = 1

Function calls with output

js
// ── Demo calls — output shown on the right ──
new NumArray([-2, 0, 3, -4, 2, -1]).sumRange(0, 2) // → 1
new NumArray([-2, 0, 3, -4, 2, -1]).sumRange(2, 5) // → -1

Back to index


26. Product of Array Except Self (Prefix)

Difficulty: Medium · Time: O(n) · Space: O(1)

Prefix/suffix product approach without division.

Approach (step by step):

  1. Left pass stores prefix products.
  2. Right pass multiplies suffix into output.
js
function productExceptSelfPrefix(arr) {
  const n = arr.length,
    out = Array(n).fill(1)
  let left = 1
  for (let i = 0; i < n; i++) {
    out[i] = left
    left *= arr[i]
  }
  let right = 1
  for (let i = n - 1; i >= 0; i--) {
    out[i] *= right
    right *= arr[i]
  }
  return out
}

Hand-written trace

js
// productExceptSelfPrefix([1,2,3,4])
// left out=[1,1,2,6]; right multiply → [24,12,8,6]

Function calls with output

js
// ── Demo calls — output shown on the right ──
productExceptSelfPrefix([1, 2, 3, 4]) // → [24, 12, 8, 6]
productExceptSelfPrefix([-1, 1, 0, -3, 3]) // → [0, 0, 9, 0, 0]

Back to index


Pattern 5 — Hash Map

27. Two Sum

Difficulty: Easy · Time: O(n) · Space: O(n)

Return indices of two numbers adding to target.

Approach (step by step):

  1. Map value → index.
  2. For each x check if target-x seen.
js
function twoSum(arr, target) {
  const map = new Map()
  for (let i = 0; i < arr.length; i++) {
    const need = target - arr[i]
    if (map.has(need)) return [map.get(need), i]
    map.set(arr[i], i)
  }
  return []
}

Hand-written trace

js
// twoSum([2,7,11,15], 9)
// i=0 map{2:0}; i=1 need=2 found → [0,1]

Function calls with output

js
// ── Demo calls — output shown on the right ──
twoSum([2, 7, 11, 15], 9) // → [0, 1]
twoSum([3, 2, 4], 6) // → [1, 2]

Back to index


28. Group Anagrams

Difficulty: Medium · Time: O(n · k log k) · Space: O(n · k)

Group strings that are anagrams.

Approach (step by step):

  1. Sort each string as key or use char count signature.
  2. Bucket into map.
js
function groupAnagrams(strs) {
  const map = new Map()
  for (const s of strs) {
    const key = [...s].sort().join('')
    if (!map.has(key)) map.set(key, [])
    map.get(key).push(s)
  }
  return [...map.values()]
}

Hand-written trace

js
// groupAnagrams(["eat","tea","tan","ate","nat","bat"])
// keys aet→3 words, ant→2, abt→bat

Function calls with output

js
// ── Demo calls — output shown on the right ──
groupAnagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat']) // → [["eat","tea","ate"],["tan","nat"],["bat"]]
groupAnagrams(['']) // → [[""]]

Back to index


29. Isomorphic Strings

Difficulty: Easy · Time: O(n) · Space: O(1)

True if s and t have same character mapping pattern.

Approach (step by step):

  1. Two maps s→t and t→s must stay consistent.
js
function isIsomorphic(s, t) {
  if (s.length !== t.length) return false
  const a = new Map(),
    b = new Map()
  for (let i = 0; i < s.length; i++) {
    if (!a.has(s[i])) a.set(s[i], t[i])
    if (!b.has(t[i])) b.set(t[i], s[i])
    if (a.get(s[i]) !== t[i] || b.get(t[i]) !== s[i]) return false
  }
  return true
}

Hand-written trace

js
// isIsomorphic("egg","add")
// e→a, g→d consistent → true

Function calls with output

js
// ── Demo calls — output shown on the right ──
isIsomorphic('egg', 'add') // → true
isIsomorphic('foo', 'bar') // → false

Back to index


30. Happy Number

Difficulty: Easy · Time: O(log n) · Space: O(log n)

Repeatedly sum squares of digits until 1 (happy) or cycle.

Approach (step by step):

  1. Use set to detect cycle.
  2. Return true if reach 1.
js
function isHappy(n) {
  const seen = new Set()
  const next = (x) =>
    String(x)
      .split('')
      .reduce((s, d) => s + d * d, 0)
  while (n !== 1 && !seen.has(n)) {
    seen.add(n)
    n = next(n)
  }
  return n === 1
}

Hand-written trace

js
undefined

Function calls with output

js
// ── Demo calls — output shown on the right ──
isHappy(19) // → true
isHappy(2) // → false

Back to index


Pattern 6 — Fast Slow Pointers

31. Linked List Cycle

Difficulty: Easy · Time: O(n) · Space: O(1)

Detect if linked list has a cycle.

Approach (step by step):

  1. Slow moves 1, fast moves 2.
  2. If they meet, cycle exists.
js
class ListNode {
  constructor(val, next = null) {
    this.val = val
    this.next = next
  }
}
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
}

Hand-written trace

js
// hasCycle 3→2→0→-4→(back to 2)
// slow:3,2,0,-4,2  fast:3,0,2,0 meet → true

Function calls with output

js
// ── Demo calls — output shown on the right ──
hasCycle(/* 3→2→0→-4↘ */) // → true
hasCycle(/* 1→2 */) // → false

Back to index


32. Linked List Cycle II

Difficulty: Medium · Time: O(n) · Space: O(1)

Return node where cycle begins, or null.

Approach (step by step):

  1. Floyd detect cycle.
  2. Reset slow to head; advance both 1 step to find entry.
js
function detectCycle(head) {
  let slow = head,
    fast = head
  while (fast && fast.next) {
    slow = slow.next
    fast = fast.next.next
    if (slow === fast) {
      slow = head
      while (slow !== fast) {
        slow = slow.next
        fast = fast.next
      }
      return slow
    }
  }
  return null
}

Hand-written trace

js
// detectCycle entry at node val=2
// meet inside cycle; reset slow=head → both reach node 2

Function calls with output

js
// ── Demo calls — output shown on the right ──
detectCycle(/* cycle entry at 2 */) // → node 2
detectCycle(/* no cycle */) // → null

Back to index


33. Happy Number (Floyd)

Difficulty: Easy · Time: O(log n) · Space: O(1)

Happy number using cycle detection instead of set.

Approach (step by step):

  1. Next function on digits.
  2. Floyd on sequence; happy iff reaches 1.
js
function isHappyFloyd(n) {
  const next = (x) =>
    String(x)
      .split('')
      .reduce((s, d) => s + d * d, 0)
  let slow = n,
    fast = next(n)
  while (fast !== 1 && slow !== fast) {
    slow = next(slow)
    fast = next(next(fast))
  }
  return fast === 1
}

Hand-written trace

js
// isHappyFloyd(19)
// slow/fast on digit-square chain → reaches 1

Function calls with output

js
// ── Demo calls — output shown on the right ──
isHappyFloyd(19) // → true
isHappyFloyd(2) // → false

Back to index


34. Find Duplicate Number (Floyd)

Difficulty: Medium · Time: O(n) · Space: O(1)

Duplicate in [1,n] array via Floyd tortoise-hare on index graph.

Approach (step by step):

  1. Map i → arr[i] as next pointer.
  2. Cycle entry is duplicate value.
js
function findDuplicateFloyd(arr) {
  let slow = arr[0],
    fast = arr[0]
  do {
    slow = arr[slow]
    fast = arr[arr[fast]]
  } while (slow !== fast)
  slow = arr[0]
  while (slow !== fast) {
    slow = arr[slow]
    fast = arr[fast]
  }
  return slow
}

Hand-written trace

js
// findDuplicateFloyd([1,3,4,2,2])
// index graph cycle entry → 2

Function calls with output

js
// ── Demo calls — output shown on the right ──
findDuplicateFloyd([1, 3, 4, 2, 2]) // → 2
findDuplicateFloyd([3, 1, 3, 4, 2]) // → 3

Back to index


Pattern 7 — Linked List

35. Reverse Linked List

Difficulty: Easy · Time: O(n) · Space: O(1)

Reverse singly linked list iteratively.

Approach (step by step):

  1. prev=null; swap next pointers while advancing.
js
function reverseList(head) {
  let prev = null,
    cur = head
  while (cur) {
    const nxt = cur.next
    cur.next = prev
    prev = cur
    cur = nxt
  }
  return prev
}

Hand-written trace

js
// reverseList 1→2→3
// prev: null,1,2  head ends 3→2→1

Function calls with output

js
// ── Demo calls — output shown on the right ──
reverseList(/* 1→2→3→4→5 */) // → 5→4→3→2→1
reverseList(/* 1→2 */) // → 2→1

Back to index


36. Merge Two Sorted Lists

Difficulty: Easy · Time: O(n + m) · Space: O(1)

Merge two sorted linked lists.

Approach (step by step):

  1. Dummy head; attach smaller node; advance pointer.
js
function mergeTwoLists(l1, l2) {
  const dummy = new ListNode(0)
  let tail = dummy
  while (l1 && l2) {
    if (l1.val <= l2.val) {
      tail.next = l1
      l1 = l1.next
    } else {
      tail.next = l2
      l2 = l2.next
    }
    tail = tail.next
  }
  tail.next = l1 || l2
  return dummy.next
}

Hand-written trace

js
// merge 1→2→4 and 1→3→4
// pick 1,1,2,3,4,4 → merged list

Function calls with output

js
// ── Demo calls — output shown on the right ──
mergeTwoLists(/*1→2→4*/, /*1→3→4*/)  // → 1→1→2→3→4→4
mergeTwoLists(null, /*0*/)  // → 0

Back to index


37. Remove Nth Node From End

Difficulty: Medium · Time: O(n) · Space: O(1)

Remove nth node from end in one pass.

Approach (step by step):

  1. Fast pointer n ahead.
  2. Move both until fast at end; delete slow.next.
js
function removeNthFromEnd(head, n) {
  const dummy = new ListNode(0, head)
  let slow = dummy,
    fast = dummy
  for (let i = 0; i <= n; i++) fast = fast.next
  while (fast) {
    slow = slow.next
    fast = fast.next
  }
  slow.next = slow.next.next
  return dummy.next
}

Hand-written trace

js
// removeNthFromEnd 1→2→3→4→5 n=2
// fast stops at null; slow before 4 → skip 5

Function calls with output

js
// ── Demo calls — output shown on the right ──
removeNthFromEnd(/*1→2→3→4→5*/, 2)  // → 1→2→3→5
removeNthFromEnd(/*1*/, 1)  // → null

Back to index


38. Add Two Numbers

Difficulty: Medium · Time: O(max(n,m)) · Space: O(max(n,m))

Add two numbers stored as reversed digit linked lists.

Approach (step by step):

  1. Add digits + carry; build result list.
js
function addTwoNumbers(l1, l2) {
  const dummy = new ListNode(0)
  let tail = dummy,
    carry = 0
  while (l1 || l2 || carry) {
    const sum = (l1?.val ?? 0) + (l2?.val ?? 0) + carry
    carry = Math.floor(sum / 10)
    tail.next = new ListNode(sum % 10)
    tail = tail.next
    l1 = l1?.next ?? null
    l2 = l2?.next ?? null
  }
  return dummy.next
}

Hand-written trace

js
// addTwoNumbers 342+465=807 → 7→0→8

Function calls with output

js
// ── Demo calls — output shown on the right ──
addTwoNumbers(/*2→4→3*/, /*5→6→4*/)  // → 7→0→8
addTwoNumbers(/*0*/, /*0*/)  // → 0

Back to index


39. Copy List With Random Pointer

Difficulty: Medium · Time: O(n) · Space: O(n)

Deep copy linked list with random pointers.

Approach (step by step):

  1. Map old node → new node.
  2. Second pass wire next and random.
js
class RandomNode {
  constructor(val) {
    this.val = val
    this.next = null
    this.random = null
  }
}
function copyRandomList(head) {
  if (!head) return null
  const map = new Map()
  let cur = head
  while (cur) {
    map.set(cur, new RandomNode(cur.val))
    cur = cur.next
  }
  cur = head
  while (cur) {
    map.get(cur).next = cur.next ? map.get(cur.next) : null
    map.get(cur).random = cur.random ? map.get(cur.random) : null
    cur = cur.next
  }
  return map.get(head)
}

Hand-written trace

js
// copyRandomList: clone each node, map old→new, wire links

Function calls with output

js
// ── Demo calls — output shown on the right ──
copyRandomList(/* [[7,null],[13,0],[11,4],[10,2],[1,0]] */) // → deep copy
copyRandomList(null) // → null

Back to index


40. Swap Nodes in Pairs

Difficulty: Medium · Time: O(n) · Space: O(1)

Swap every two adjacent nodes.

Approach (step by step):

  1. Dummy before head; reverse pairs iteratively.
js
function swapPairs(head) {
  const dummy = new ListNode(0, head)
  let prev = dummy
  while (prev.next && prev.next.next) {
    const a = prev.next,
      b = a.next
    a.next = b.next
    b.next = a
    prev.next = b
    prev = a
  }
  return dummy.next
}

Hand-written trace

js
// swapPairs 1→2→3→4
// swap(1,2) then (3,4) → 2→1→4→3

Function calls with output

js
// ── Demo calls — output shown on the right ──
swapPairs(/*1→2→3→4*/) // → 2→1→4→3
swapPairs(/*1→2→3*/) // → 2→1→3

Back to index


41. Rotate List

Difficulty: Medium · Time: O(n) · Space: O(1)

Rotate list to the right by k places.

Approach (step by step):

  1. Find length; k%=n; connect tail to head; break at new tail.
js
function rotateRight(head, k) {
  if (!head || !head.next || !k) return head
  let len = 1,
    tail = head
  while (tail.next) {
    len++
    tail = tail.next
  }
  k %= len
  if (!k) return head
  tail.next = head
  let steps = len - k,
    cur = tail
  while (steps--) cur = cur.next
  const newHead = cur.next
  cur.next = null
  return newHead
}

Hand-written trace

js
// rotateRight 1→2→3→4→5 k=2 len=5
// tail→head; break before node4 → 4→5→1→2→3

Function calls with output

js
// ── Demo calls — output shown on the right ──
rotateRight(/*1→2→3→4→5*/, 2)  // → 4→5→1→2→3
rotateRight(/*0→1→2*/, 4)  // → 2→0→1

Back to index


Pattern 8 — Binary Search

42. Binary Search

Difficulty: Easy · Time: O(log n) · Space: O(1)

Find target index in sorted array or -1.

Approach (step by step):

  1. lo/hi bounds; mid compare; narrow half.
js
function binarySearch(arr, target) {
  let lo = 0,
    hi = arr.length - 1
  while (lo <= hi) {
    const mid = (lo + hi) >> 1
    if (arr[mid] === target) return mid
    if (arr[mid] < target) lo = mid + 1
    else hi = mid - 1
  }
  return -1
}

Hand-written trace

js
// binarySearch([-1,0,3,5,9,12], 9)
// lo,hi: 0,5 mid=2 val3<9 → lo=3
// 3,5 mid=4 val9 → return 4

Function calls with output

js
// ── Demo calls — output shown on the right ──
binarySearch([-1, 0, 3, 5, 9, 12], 9) // → 4
binarySearch([-1, 0, 3, 5, 9, 12], 2) // → -1

Back to index


43. Search in Rotated Sorted Array

Difficulty: Medium · Time: O(log n) · Space: O(1)

Search target in rotated sorted array with distinct values.

Approach (step by step):

  1. Identify sorted half; check if target in range; narrow.
js
function searchRotated(arr, target) {
  let lo = 0,
    hi = arr.length - 1
  while (lo <= hi) {
    const mid = (lo + hi) >> 1
    if (arr[mid] === target) return mid
    if (arr[lo] <= arr[mid]) {
      if (arr[lo] <= target && target < arr[mid]) hi = mid - 1
      else lo = mid + 1
    } else {
      if (arr[mid] < target && target <= arr[hi]) lo = mid + 1
      else hi = mid - 1
    }
  }
  return -1
}

Hand-written trace

js
// searchRotated([4,5,6,7,0,1,2], 0)
// left half sorted; 0 not in [4,7) → lo=mid+1 → find idx4

Function calls with output

js
// ── Demo calls — output shown on the right ──
searchRotated([4, 5, 6, 7, 0, 1, 2], 0) // → 4
searchRotated([4, 5, 6, 7, 0, 1, 2], 3) // → -1

Back to index


44. Find Peak Element

Difficulty: Medium · Time: O(log n) · Space: O(1)

Find index of any peak where arr[i] > neighbors.

Approach (step by step):

  1. If mid < mid+1, peak on right; else on left.
js
function findPeakElement(arr) {
  let lo = 0,
    hi = arr.length - 1
  while (lo < hi) {
    const mid = (lo + hi) >> 1
    if (arr[mid] < arr[mid + 1]) lo = mid + 1
    else hi = mid
  }
  return lo
}

Hand-written trace

js
// findPeakElement([1,2,3,1])
// mid=1 2<3 → lo=2; lo=hi=2 peak idx2 val3

Function calls with output

js
// ── Demo calls — output shown on the right ──
findPeakElement([1, 2, 3, 1]) // → 2
findPeakElement([1, 2, 1, 3, 5, 6, 4]) // → 5

Back to index


45. Median of Two Sorted Arrays

Difficulty: Hard · Time: O(log min(n,m)) · Space: O(1)

Median of two sorted arrays in logarithmic time.

Approach (step by step):

  1. Binary search partition on smaller array.
  2. Ensure left max ≤ right min.
js
function findMedianSortedArrays(a, b) {
  if (a.length > b.length) [a, b] = [b, a]
  const m = a.length,
    n = b.length,
    half = (m + n + 1) >> 1
  let lo = 0,
    hi = m
  while (lo <= hi) {
    const i = (lo + hi) >> 1,
      j = half - i
    const aL = i ? a[i - 1] : -Infinity,
      aR = i < m ? a[i] : Infinity
    const bL = j ? b[j - 1] : -Infinity,
      bR = j < n ? b[j] : Infinity
    if (aL <= bR && bL <= aR) {
      return (m + n) % 2 ? Math.max(aL, bL) : (Math.max(aL, bL) + Math.min(aR, bR)) / 2
    }
    if (aL > bR) hi = i - 1
    else lo = i + 1
  }
  return 0
}

Hand-written trace

js
// findMedianSortedArrays([1,3],[2])
// partition i=1,j=0 → left [1] right [2,3] median=2

Function calls with output

js
// ── Demo calls — output shown on the right ──
findMedianSortedArrays([1, 3], [2]) // → 2
findMedianSortedArrays([1, 2], [3, 4]) // → 2.5

Back to index


Pattern 9 — Stack

46. Valid Parentheses

Difficulty: Easy · Time: O(n) · Space: O(n)

True if brackets are correctly closed and nested.

Approach (step by step):

  1. Stack opening brackets; pop on matching close.
js
function isValid(s) {
  const map = { ')': '(', ']': '[', '}': '{' }
  const st = []
  for (const c of s) {
    if ('([{'.includes(c)) st.push(c)
    else if (!st.length || st.pop() !== map[c]) return false
  }
  return st.length === 0
}

Hand-written trace

js
// isValid("()[]{}")
// push ( [ { ; pop matches → empty stack true

Function calls with output

js
// ── Demo calls — output shown on the right ──
isValid('()[]{}') // → true
isValid('(]') // → false

Back to index


47. Daily Temperatures

Difficulty: Medium · Time: O(n) · Space: O(n)

Days until warmer temperature for each day.

Approach (step by step):

  1. Monotonic decreasing stack of indices.
js
function dailyTemperatures(arr) {
  const out = Array(arr.length).fill(0),
    st = []
  for (let i = 0; i < arr.length; i++) {
    while (st.length && arr[i] > arr[st.at(-1)]) {
      const j = st.pop()
      out[j] = i - j
    }
    st.push(i)
  }
  return out
}

Hand-written trace

js
// dailyTemperatures([73,74,75,...])
// stack resolves warmer days → wait counts

Function calls with output

js
// ── Demo calls — output shown on the right ──
dailyTemperatures([73, 74, 75, 71, 69, 72, 76, 73]) // → [1,1,4,2,1,1,0,0]
dailyTemperatures([30, 40, 50, 60]) // → [1,1,1,0]

Back to index


48. Next Greater Element

Difficulty: Medium · Time: O(n) · Space: O(n)

Next greater element to the right for each index.

Approach (step by step):

  1. Monotonic stack; pop smaller when larger seen.
js
function nextGreaterElements(arr) {
  const out = Array(arr.length).fill(-1),
    st = []
  for (let i = 0; i < arr.length; i++) {
    while (st.length && arr[i] > arr[st.at(-1)]) {
      out[st.pop()] = arr[i]
    }
    st.push(i)
  }
  return out
}

Hand-written trace

js
// nextGreaterElements([2,1,2,4,3])
// idx0→4 idx1→2 idx2→4 idx3,4→-1

Function calls with output

js
// ── Demo calls — output shown on the right ──
nextGreaterElements([2, 1, 2, 4, 3]) // → [4,2,4,-1,-1]
nextGreaterElements([5, 4, 3, 2, 1]) // → [-1,-1,-1,-1,-1]

Back to index


49. Largest Rectangle in Histogram

Difficulty: Hard · Time: O(n) · Space: O(n)

Largest rectangle area in histogram.

Approach (step by step):

  1. Stack increasing bars; compute width on pop.
js
function largestRectangleArea(heights) {
  const st = [],
    n = heights.length
  let best = 0
  for (let i = 0; i <= n; i++) {
    const h = i === n ? 0 : heights[i]
    while (st.length && h < heights[st.at(-1)]) {
      const hi = st.pop()
      const w = st.length ? i - st.at(-1) - 1 : i
      best = Math.max(best, heights[hi] * w)
    }
    st.push(i)
  }
  return best
}

Hand-written trace

js
// largestRectangleArea([2,1,5,6,2,3])
// max area 5*2=10 between bars height5

Function calls with output

js
// ── Demo calls — output shown on the right ──
largestRectangleArea([2, 1, 5, 6, 2, 3]) // → 10
largestRectangleArea([2, 4]) // → 4

Back to index


50. 132 Pattern

Difficulty: Medium · Time: O(n) · Space: O(n)

True if there exist i < j < k with arr[i] < arr[k] < arr[j].

Approach (step by step):

  1. Monotonic stack from right; track best third (ak).
js
function find132pattern(arr) {
  const st = []
  let ak = -Infinity
  for (let i = arr.length - 1; i >= 0; i--) {
    if (arr[i] < ak) return true
    while (st.length && arr[i] > st.at(-1)) ak = st.pop()
    st.push(arr[i])
  }
  return false
}

Hand-written trace

js
// find132pattern([3,1,4,2])
// i=0 val3: ak becomes 2 from stack → 1<2 pattern true

Function calls with output

js
// ── Demo calls — output shown on the right ──
find132pattern([3, 1, 4, 2]) // → true
find132pattern([1, 2, 3, 4]) // → false

Back to index


Pattern 10 — Heap

51. Top K Frequent Elements

Difficulty: Medium · Time: O(n log k) · Space: O(n)

Return k most frequent elements.

Approach (step by step):

  1. Count frequencies.
  2. Min-heap of size k by frequency.
js
function topKFrequent(arr, k) {
  const freq = new Map()
  for (const x of arr) freq.set(x, (freq.get(x) ?? 0) + 1)
  const buckets = Array.from(freq.entries()).sort((a, b) => b[1] - a[1])
  return buckets.slice(0, k).map(([x]) => x)
}

Hand-written trace

js
// topKFrequent([1,1,1,2,2,3], 2)
// freq 1:3 2:2 3:1 → top [1,2]

Function calls with output

js
// ── Demo calls — output shown on the right ──
topKFrequent([1, 1, 1, 2, 2, 3], 2) // → [1, 2]
topKFrequent([1], 1) // → [1]

Back to index


52. K Closest Points to Origin

Difficulty: Medium · Time: O(n log k) · Space: O(k)

Return k points closest to origin.

Approach (step by step):

  1. Max-heap by distance or quickselect.
  2. Keep k smallest distances.
js
function kClosest(points, k) {
  const dist = ([x, y]) => x * x + y * y
  return points.sort((a, b) => dist(a) - dist(b)).slice(0, k)
}

Hand-written trace

js
// kClosest([[1,3],[-2,2]], 1)
// dist 10 vs 8 → [-2,2] closer

Function calls with output

js
// ── Demo calls — output shown on the right ──
kClosest(
  [
    [1, 3],
    [-2, 2],
  ],
  1,
) // → [[-2,2]]
kClosest(
  [
    [3, 3],
    [5, -1],
    [-2, 4],
  ],
  2,
) // → [[3,3],[-2,4]]

Back to index


53. Find Median from Data Stream

Difficulty: Hard · Time: O(log n) per add · Space: O(n)

Maintain median of incoming numbers.

Approach (step by step):

  1. Two heaps: max-heap lower half, min-heap upper half.
  2. Balance sizes and read tops.
js
class MedianFinder {
  constructor() {
    this.lo = []
    this.hi = []
  }
  _push(heap, val, isMax) {
    heap.push(val)
    heap.sort((a, b) => (isMax ? b - a : a - b))
  }
  addNum(num) {
    if (!this.lo.length || num <= this.lo[0]) this._push(this.lo, num, true)
    else this._push(this.hi, num, false)
    if (this.lo.length > this.hi.length + 1) this._push(this.hi, this.lo.pop(), false)
    else if (this.hi.length > this.lo.length) this._push(this.lo, this.hi.shift(), true)
  }
  findMedian() {
    return this.lo.length > this.hi.length ? this.lo[0] : (this.lo[0] + this.hi[0]) / 2
  }
}

Hand-written trace

js
// add 1,2 → lo=[1,2]? balanced lo=[2] hi=[1] median 1.5
// add 3 → median 2

Function calls with output

js
// ── Demo calls — output shown on the right ──
;((mf = new MedianFinder()),
mf.addNum(1),
mf.addNum(2),
mf.findMedian()) // → 1.5
(mf.addNum(3), mf.findMedian()) // → 2

Back to index


54. Merge K Sorted Lists

Difficulty: Hard · Time: O(n log k) · Space: O(k)

Merge k sorted linked lists.

Approach (step by step):

  1. Min-heap of list heads by value.
  2. Pop, attach, push next.
js
function mergeKLists(lists) {
  const nodes = lists.filter(Boolean).sort((a, b) => a.val - b.val)
  const dummy = new ListNode(0)
  let tail = dummy
  while (nodes.length) {
    const node = nodes.shift()
    tail.next = node
    tail = tail.next
    if (node.next) {
      let i = 0
      while (i < nodes.length && nodes[i].val <= node.next.val) i++
      nodes.splice(i, 0, node.next)
    }
  }
  return dummy.next
}

Hand-written trace

js
// mergeKLists three lists → pop smallest heads in order

Function calls with output

js
// ── Demo calls — output shown on the right ──
mergeKLists([
  ,
  ,/*1→4→5*/
  /*1→3→4*/
  /*2→6*/
]) // → 1→1→2→3→4→4→5→6
mergeKLists([]) // → null

Back to index


Pattern 11 — Backtracking

55. Subsets

Difficulty: Medium · Time: O(n · 2^n) · Space: O(n)

Return all subsets of distinct integers.

Approach (step by step):

  1. Start empty subset; at each index choose include or skip.
js
function subsets(arr) {
  const out = [[]]
  for (const x of arr) {
    const len = out.length
    for (let i = 0; i < len; i++) out.push([...out[i], x])
  }
  return out
}

Hand-written trace

js
// subsets([1,2,3])
// add 1, then 2, then 3 to prior subsets → 8 subsets

Function calls with output

js
// ── Demo calls — output shown on the right ──
subsets([1, 2, 3]) // → [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
subsets([0]) // → [[],[0]]

Back to index


56. Permutations

Difficulty: Medium · Time: O(n · n!) · Space: O(n)

Return all permutations of distinct nums.

Approach (step by step):

  1. Backtrack with used array; swap or choose from remaining.
js
function permute(arr) {
  const out = [],
    path = [],
    used = Array(arr.length).fill(false)
  const dfs = () => {
    if (path.length === arr.length) {
      out.push([...path])
      return
    }
    for (let i = 0; i < arr.length; i++) {
      if (used[i]) continue
      used[i] = true
      path.push(arr[i])
      dfs()
      path.pop()
      used[i] = false
    }
  }
  dfs()
  return out
}

Hand-written trace

js
// permute([1,2,3])
// dfs builds [1,2,3],[1,3,2],[2,1,3]...

Function calls with output

js
// ── Demo calls — output shown on the right ──
permute([1, 2, 3]) // → 6 permutations
permute([0, 1]) // → [[0,1],[1,0]]

Back to index


57. Combination Sum

Difficulty: Medium · Time: O(2^n) · Space: O(target)

Combinations summing to target; reuse candidates allowed.

Approach (step by step):

  1. Sort; recurse from index allowing reuse.
  2. Backtrack on exceed.
js
function combinationSum(candidates, target) {
  candidates.sort((a, b) => a - b)
  const out = [],
    path = []
  const dfs = (start, sum) => {
    if (sum === target) {
      out.push([...path])
      return
    }
    if (sum > target) return
    for (let i = start; i < candidates.length; i++) {
      path.push(candidates[i])
      dfs(i, sum + candidates[i])
      path.pop()
    }
  }
  dfs(0, 0)
  return out
}

Hand-written trace

js
// combinationSum([2,3,6,7], 7)
// paths 2+2+3 and 7

Function calls with output

js
// ── Demo calls — output shown on the right ──
combinationSum([2, 3, 6, 7], 7) // → [[2,2,3],[7]]
combinationSum([2, 3, 5], 8) // → [[2,2,2,2],[2,3,3],[3,5]]

Back to index


58. Word Search

Difficulty: Medium · Time: O(m · n · 4^L) · Space: O(L)

True if word exists in grid by adjacent cells.

Approach (step by step):

  1. DFS from each cell; mark visited; backtrack.
js
function exist(board, word) {
  const rows = board.length,
    cols = board[0].length
  const dfs = (r, c, i) => {
    if (i === word.length) return true
    if (r < 0 || c < 0 || r >= rows || c >= cols || board[r][c] !== word[i]) return false
    const tmp = board[r][c]
    board[r][c] = '#'
    const ok = dfs(r + 1, c, i + 1) || dfs(r - 1, c, i + 1) || dfs(r, c + 1, i + 1) || dfs(r, c - 1, i + 1)
    board[r][c] = tmp
    return ok
  }
  for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) if (dfs(r, c, 0)) return true
  return false
}

Hand-written trace

js
// exist board ABCCED path from A→B→C→C→E→D

Function calls with output

js
// ── Demo calls — output shown on the right ──
exist(
  [
    ['A', 'B', 'C', 'E'],
    ['S', 'F', 'C', 'S'],
    ['A', 'D', 'E', 'E'],
  ],
  'ABCCED',
) // → true
exist(
  [
    ['A', 'B'],
    ['C', 'D'],
  ],
  'ABCD',
) // → false

Back to index


59. N-Queens

Difficulty: Hard · Time: O(n!) · Space: O(n²)

Return all distinct n-queens board configurations.

Approach (step by step):

  1. Place row by row; track cols and diagonals.
  2. Backtrack on conflict.
js
function solveNQueens(n) {
  const out = [],
    cols = new Set(),
    d1 = new Set(),
    d2 = new Set(),
    board = Array.from({ length: n }, () => Array(n).fill('.'))
  const toStr = () => board.map((row) => row.join(''))
  const dfs = (r) => {
    if (r === n) {
      out.push(toStr())
      return
    }
    for (let c = 0; c < n; c++) {
      if (cols.has(c) || d1.has(r - c) || d2.has(r + c)) continue
      cols.add(c)
      d1.add(r - c)
      d2.add(r + c)
      board[r][c] = 'Q'
      dfs(r + 1)
      board[r][c] = '.'
      cols.delete(c)
      d1.delete(r - c)
      d2.delete(r + c)
    }
  }
  dfs(0)
  return out
}

Hand-written trace

js
// solveNQueens(4)
// place Q rows 0..3 avoiding attacks → 2 solutions

Function calls with output

js
// ── Demo calls — output shown on the right ──
solveNQueens(4).length // → 2
solveNQueens(1) // → [["Q"]]

Back to index


Pattern 12 — Trees DFS

60. Maximum Depth of Binary Tree

Difficulty: Easy · Time: O(n) · Space: O(h)

Maximum depth (root to farthest leaf).

Approach (step by step):

  1. Base null → 0.
  2. Return 1 + max(left, right).
js
class TreeNode {
  constructor(val, left = null, right = null) {
    this.val = val
    this.left = left
    this.right = right
  }
}
function maxDepth(root) {
  if (!root) return 0
  return 1 + Math.max(maxDepth(root.left), maxDepth(root.right))
}

Hand-written trace

js
// maxDepth root3 left9 depth1 right20 depth2+1=3

Function calls with output

js
// ── Demo calls — output shown on the right ──
maxDepth(/* [3,9,20,null,null,15,7] */) // → 3
maxDepth(/* [1,null,2] */) // → 2

Back to index


61. Path Sum

Difficulty: Easy · Time: O(n) · Space: O(h)

True if root-to-leaf path sums to target.

Approach (step by step):

  1. Subtract node val from target at each step.
  2. Leaf check remaining === 0.
js
function hasPathSum(root, target) {
  if (!root) return false
  if (!root.left && !root.right) return target === root.val
  return hasPathSum(root.left, target - root.val) || hasPathSum(root.right, target - root.val)
}

Hand-written trace

js
// hasPathSum target22 path 5→4→11→2

Function calls with output

js
// ── Demo calls — output shown on the right ──
hasPathSum(/* [5,4,8,11,null,13,4,7,2,null,null,null,1] */, 22)  // → true
hasPathSum(/* [1,2], 3 */)  // → true

Back to index


62. Diameter of Binary Tree

Difficulty: Easy · Time: O(n) · Space: O(h)

Longest path between any two nodes (edges count).

Approach (step by step):

  1. Post-order height; update best with left+right heights.
js
function diameterOfBinaryTree(root) {
  let best = 0
  const height = (node) => {
    if (!node) return 0
    const l = height(node.left),
      r = height(node.right)
    best = Math.max(best, l + r)
    return 1 + Math.max(l, r)
  }
  height(root)
  return best
}

Hand-written trace

js
// diameter tree 1-2-4 path length 3 edges via 4-2-1-3

Function calls with output

js
// ── Demo calls — output shown on the right ──
diameterOfBinaryTree(/* [1,2,3,4,5] */) // → 3
diameterOfBinaryTree(/* [1,2] */) // → 1

Back to index


63. Lowest Common Ancestor

Difficulty: Medium · Time: O(n) · Space: O(h)

LCA of two nodes in binary tree.

Approach (step by step):

  1. If node is p or q return it.
  2. Recurse both sides; if both non-null current is LCA.
js
function lowestCommonAncestor(root, p, q) {
  if (!root || root === p || root === q) return root
  const l = lowestCommonAncestor(root.left, p, q)
  const r = lowestCommonAncestor(root.right, p, q)
  if (l && r) return root
  return l || r
}

Hand-written trace

js
// LCA(5,1) in tree → node3 splits subtrees containing both

Function calls with output

js
// ── Demo calls — output shown on the right ──
lowestCommonAncestor(/* tree */, node5, node1)  // → 3
lowestCommonAncestor(/* tree */, node5, node4)  // → 5

Back to index


Pattern 13 — Trees BFS

64. Binary Tree Level Order Traversal

Difficulty: Medium · Time: O(n) · Space: O(n)

Return values level by level.

Approach (step by step):

  1. BFS queue; process level size at a time.
js
function levelOrder(root) {
  if (!root) return []
  const out = [],
    q = [root]
  while (q.length) {
    const size = q.length,
      level = []
    for (let i = 0; i < size; i++) {
      const node = q.shift()
      level.push(node.val)
      if (node.left) q.push(node.left)
      if (node.right) q.push(node.right)
    }
    out.push(level)
  }
  return out
}

Hand-written trace

js
// levelOrder → [[3],[9,20],[15,7]]

Function calls with output

js
// ── Demo calls — output shown on the right ──
levelOrder(/* [3,9,20,null,null,15,7] */) // → [[3],[9,20],[15,7]]
levelOrder(/* [1] */) // → [[1]]

Back to index


65. Binary Tree Right Side View

Difficulty: Medium · Time: O(n) · Space: O(n)

Values visible from right side.

Approach (step by step):

  1. BFS; take last node of each level.
js
function rightSideView(root) {
  if (!root) return []
  const out = [],
    q = [root]
  while (q.length) {
    const size = q.length
    for (let i = 0; i < size; i++) {
      const node = q.shift()
      if (i === size - 1) out.push(node.val)
      if (node.left) q.push(node.left)
      if (node.right) q.push(node.right)
    }
  }
  return out
}

Hand-written trace

js
// rightSideView levels last: 1, 3, 4

Function calls with output

js
// ── Demo calls — output shown on the right ──
rightSideView(/* [1,2,3,null,5,null,4] */) // → [1,3,4]
rightSideView(/* [1,null,3] */) // → [1,3]

Back to index


66. Minimum Depth of Binary Tree

Difficulty: Easy · Time: O(n) · Space: O(n)

Minimum root-to-leaf depth.

Approach (step by step):

  1. BFS until first leaf; return depth.
js
function minDepth(root) {
  if (!root) return 0
  const q = [[root, 1]]
  while (q.length) {
    const [node, d] = q.shift()
    if (!node.left && !node.right) return d
    if (node.left) q.push([node.left, d + 1])
    if (node.right) q.push([node.right, d + 1])
  }
  return 0
}

Hand-written trace

js
// minDepth BFS hits leaf 9 at depth 2

Function calls with output

js
// ── Demo calls — output shown on the right ──
minDepth(/* [3,9,20,null,null,15,7] */) // → 2
minDepth(/* [2,null,3,null,4] */) // → 3

Back to index


Pattern 14 — BST

67. Validate BST

Difficulty: Medium · Time: O(n) · Space: O(h)

True if tree is valid binary search tree.

Approach (step by step):

  1. Pass min/max bounds down; left < node < right.
js
function isValidBST(root, lo = -Infinity, hi = Infinity) {
  if (!root) return true
  if (root.val <= lo || root.val >= hi) return false
  return isValidBST(root.left, lo, root.val) && isValidBST(root.right, root.val, hi)
}

Hand-written trace

js
// isValidBST [5,1,4,...] node4 < min5 on right subtree → false

Function calls with output

js
// ── Demo calls — output shown on the right ──
isValidBST(/* [2,1,3] */) // → true
isValidBST(/* [5,1,4,null,null,3,6] */) // → false

Back to index


68. Kth Smallest Element in BST

Difficulty: Medium · Time: O(h + k) · Space: O(h)

Return kth smallest value in BST.

Approach (step by step):

  1. Inorder traversal; count nodes.
js
function kthSmallest(root, k) {
  const st = []
  let cur = root
  while (cur || st.length) {
    while (cur) {
      st.push(cur)
      cur = cur.left
    }
    cur = st.pop()
    if (--k === 0) return cur.val
    cur = cur.right
  }
  return -1
}

Hand-written trace

js
// kthSmallest inorder 1,2,3,4 k=3 → 3

Function calls with output

js
// ── Demo calls — output shown on the right ──
kthSmallest(/* [3,1,4,null,2] */, 1)  // → 1
kthSmallest(/* [5,3,6,2,4,null,null,1] */, 3)  // → 3

Back to index


69. Convert Sorted Array to BST

Difficulty: Easy · Time: O(n) · Space: O(n)

Build height-balanced BST from sorted array.

Approach (step by step):

  1. Pick mid as root; recurse on left and right halves.
js
function sortedArrayToBST(arr) {
  const build = (l, r) => {
    if (l > r) return null
    const m = (l + r) >> 1
    return new TreeNode(arr[m], build(l, m - 1), build(m + 1, r))
  }
  return build(0, arr.length - 1)
}

Hand-written trace

js
// sortedArrayToBST [-10,-3,0,5,9]
// root 0 left [-10,-3] right [5,9]

Function calls with output

js
// ── Demo calls — output shown on the right ──
sortedArrayToBST([-10, -3, 0, 5, 9]) // → balanced BST
sortedArrayToBST([1, 3]) // → root 3 left 1

Back to index


Pattern 15 — Trie

70. Implement Trie

Difficulty: Medium · Time: O(m) per op · Space: O(n · m)

Insert, search, and startsWith on dictionary of words.

Approach (step by step):

  1. Node map of children; end flag on word completion.
js
class TrieNode {
  constructor() {
    this.children = new Map()
    this.end = false
  }
}
class Trie {
  constructor() {
    this.root = new TrieNode()
  }
  insert(word) {
    let node = this.root
    for (const c of word) {
      if (!node.children.has(c)) node.children.set(c, new TrieNode())
      node = node.children.get(c)
    }
    node.end = true
  }
  search(word) {
    const node = this._walk(word)
    return !!node && node.end
  }
  startsWith(prefix) {
    return !!this._walk(prefix)
  }
  _walk(s) {
    let node = this.root
    for (const c of s) {
      if (!node.children.has(c)) return null
      node = node.children.get(c)
    }
    return node
  }
}

Hand-written trace

js
// Trie insert apple → search apple true, app false, startsWith app true

Function calls with output

js
// ── Demo calls — output shown on the right ──
;((t = new Trie()),
t.insert('apple'),
t.search(
  'apple',
)) // → true
(t.search('app'), t.startsWith('app')) // → false, true

Back to index


71. Word Search II

Difficulty: Hard · Time: O(m · n · 4^L) · Space: O(W · L)

Find all words from dictionary on board.

Approach (step by step):

  1. Build trie of words.
  2. DFS board pruning by trie paths.
js
function findWords(board, words) {
  const root = { children: {}, end: false }
  for (const w of words) {
    let node = root
    for (const c of w) {
      node.children[c] ??= { children: {}, end: false }
      node = node.children[c]
    }
    node.end = w
  }
  const out = new Set(),
    rows = board.length,
    cols = board[0].length
  const dfs = (r, c, node) => {
    const ch = board[r][c]
    if (!node.children[ch]) return
    node = node.children[ch]
    if (node.end) {
      out.add(node.end)
      node.end = false
    }
    board[r][c] = '#'
    if (r) dfs(r - 1, c, node)
    if (r + 1 < rows) dfs(r + 1, c, node)
    if (c) dfs(r, c - 1, node)
    if (c + 1 < cols) dfs(r, c + 1, node)
    board[r][c] = ch
  }
  for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) dfs(r, c, root)
  return [...out]
}

Hand-written trace

js
// findWords board → DFS trie finds eat, oath

Function calls with output

js
// ── Demo calls — output shown on the right ──
findWords(
  [
    ['o', 'a', 'a', 'n'],
    ['e', 't', 'a', 'e'],
    ['i', 'h', 'k', 'r'],
    ['i', 'f', 'l', 'v'],
  ],
  ['oath', 'pea', 'eat', 'rain'],
) // → ["eat","oath"]
findWords(
  [
    ['a', 'b'],
    ['c', 'd'],
  ],
  ['abcb'],
) // → []

Back to index


72. Design Add and Search Words

Difficulty: Medium · Time: O(m) add, O(26^dots) search · Space: O(n · m)

Dictionary with add(word) and search(word with . wildcards).

Approach (step by step):

  1. Trie insert; search DFS on dot branches.
js
class WordDictionary {
  constructor() {
    this.root = { children: {} }
  }
  addWord(word) {
    let node = this.root
    for (const c of word) {
      node.children[c] ??= { children: {} }
      node = node.children[c]
    }
    node.end = true
  }
  search(word) {
    const dfs = (i, node) => {
      if (i === word.length) return !!node.end
      const c = word[i]
      if (c === '.') {
        for (const k in node.children) if (dfs(i + 1, node.children[k])) return true
        return false
      }
      if (!node.children[c]) return false
      return dfs(i + 1, node.children[c])
    }
    return dfs(0, this.root)
  }
}

Hand-written trace

js
// WordDictionary .ad matches bad via wildcard DFS

Function calls with output

js
// ── Demo calls — output shown on the right ──
;((d = new WordDictionary()),
d.addWord('bad'),
d.search(
  'bad',
)) // → true
(d.search('.ad'), d.search('b..')) // → true, true

Back to index


Pattern 16 — Graphs

73. Number of Islands

Difficulty: Medium · Time: O(m · n) · Space: O(m · n)

Count connected land regions in grid.

Approach (step by step):

  1. Scan grid; DFS/BFS flood fill each unseen land cell.
js
function numIslands(grid) {
  if (!grid.length) return 0
  const rows = grid.length,
    cols = grid[0].length
  let count = 0
  const dfs = (r, c) => {
    if (r < 0 || c < 0 || r >= rows || c >= cols || grid[r][c] !== '1') return
    grid[r][c] = '0'
    dfs(r + 1, c)
    dfs(r - 1, c)
    dfs(r, c + 1)
    dfs(r, c - 1)
  }
  for (let r = 0; r < rows; r++)
    for (let c = 0; c < cols; c++)
      if (grid[r][c] === '1') {
        count++
        dfs(r, c)
      }
  return count
}

Hand-written trace

js
// numIslands 4x5 grid → flood fill 1 island

Function calls with output

js
// ── Demo calls — output shown on the right ──
numIslands([
  ['1', '1', '1', '1', '0'],
  ['1', '1', '0', '1', '0'],
  ['1', '1', '0', '0', '0'],
  ['0', '0', '0', '0', '0'],
]) // → 1
numIslands([
  ['1', '1', '0', '0', '0'],
  ['1', '1', '0', '0', '0'],
  ['0', '0', '1', '0', '0'],
  ['0', '0', '0', '1', '1'],
]) // → 3

Back to index


74. Clone Graph

Difficulty: Medium · Time: O(V + E) · Space: O(V)

Deep copy connected undirected graph.

Approach (step by step):

  1. Map old node → clone; BFS/DFS copy neighbors.
js
class GraphNode {
  constructor(val, neighbors = []) {
    this.val = val
    this.neighbors = neighbors
  }
}
function cloneGraph(node) {
  if (!node) return null
  const map = new Map([[node, new GraphNode(node.val)]])
  const q = [node]
  while (q.length) {
    const cur = q.shift()
    for (const nei of cur.neighbors) {
      if (!map.has(nei)) {
        map.set(nei, new GraphNode(nei.val))
        q.push(nei)
      }
      map.get(cur).neighbors.push(map.get(nei))
    }
  }
  return map.get(node)
}

Hand-written trace

js
// cloneGraph BFS map each node → clone neighbors

Function calls with output

js
// ── Demo calls — output shown on the right ──
cloneGraph(/* adj list [[2,4],[1,3],[2,4],[1,3]] */) // → deep copy
cloneGraph(null) // → null

Back to index


75. Course Schedule

Difficulty: Medium · Time: O(V + E) · Space: O(V + E)

Can finish all courses given prerequisites?

Approach (step by step):

  1. Build graph; Kahn topological sort or DFS cycle detect.
js
function canFinish(numCourses, prerequisites) {
  const adj = Array.from({ length: numCourses }, () => [])
  const indeg = Array(numCourses).fill(0)
  for (const [a, b] of prerequisites) {
    adj[b].push(a)
    indeg[a]++
  }
  const q = indeg.map((d, i) => (d === 0 ? i : -1)).filter((x) => x >= 0)
  let seen = 0
  while (q.length) {
    const u = q.shift()
    seen++
    for (const v of adj[u]) if (--indeg[v] === 0) q.push(v)
  }
  return seen === numCourses
}

Hand-written trace

js
// canFinish 2 courses [[1,0]] topo order 0→1 → true

Function calls with output

js
// ── Demo calls — output shown on the right ──
canFinish(2, [[1, 0]]) // → true
canFinish(2, [
  [1, 0],
  [0, 1],
]) // → false

Back to index


76. Pacific Atlantic Water Flow

Difficulty: Medium · Time: O(m · n) · Space: O(m · n)

Cells that can flow to both Pacific and Atlantic oceans.

Approach (step by step):

  1. DFS from ocean borders inward against height.
  2. Intersect reachable sets.
js
function pacificAtlantic(heights) {
  const rows = heights.length,
    cols = heights[0].length
  const pac = Array.from({ length: rows }, () => Array(cols).fill(false))
  const atl = Array.from({ length: rows }, () => Array(cols).fill(false))
  const dfs = (r, c, seen) => {
    seen[r][c] = true
    for (const [dr, dc] of [
      [1, 0],
      [-1, 0],
      [0, 1],
      [0, -1],
    ]) {
      const nr = r + dr,
        nc = c + dc
      if (nr >= 0 && nc >= 0 && nr < rows && nc < cols && !seen[nr][nc] && heights[nr][nc] >= heights[r][c])
        dfs(nr, nc, seen)
    }
  }
  for (let c = 0; c < cols; c++) {
    dfs(0, c, pac)
    dfs(rows - 1, c, atl)
  }
  for (let r = 0; r < rows; r++) {
    dfs(r, 0, pac)
    dfs(r, cols - 1, atl)
  }
  const out = []
  for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) if (pac[r][c] && atl[r][c]) out.push([r, c])
  return out
}

Hand-written trace

js
// pacificAtlantic reverse DFS from both oceans → intersection cells

Function calls with output

js
// ── Demo calls — output shown on the right ──
pacificAtlantic([
  [1, 2, 2, 3, 5],
  [3, 2, 3, 4, 4],
  [2, 4, 5, 3, 1],
  [6, 7, 1, 4, 5],
  [5, 1, 1, 2, 4],
]) // → [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
pacificAtlantic([[1]]) // → [[0,0]]

Back to index


Pattern 17 — Union Find

77. Number of Connected Components

Difficulty: Medium · Time: O(n + e · α(n)) · Space: O(n)

Count connected components in undirected graph with n nodes.

Approach (step by step):

  1. Union-Find: union edges; count distinct roots.
js
function countComponents(n, edges) {
  const parent = Array.from({ length: n }, (_, i) => i)
  const find = (x) => (parent[x] === x ? x : (parent[x] = find(parent[x])))
  const unite = (a, b) => {
    parent[find(a)] = find(b)
  }
  for (const [a, b] of edges) unite(a, b)
  let comps = 0
  for (let i = 0; i < n; i++) if (find(i) === i) comps++
  return comps
}

Hand-written trace

js
// countComponents n=5 edges 0-1-2 and 3-4 → 2 components

Function calls with output

js
// ── Demo calls — output shown on the right ──
countComponents(5, [
  [0, 1],
  [1, 2],
  [3, 4],
]) // → 2
countComponents(5, [
  [0, 1],
  [1, 2],
  [2, 3],
  [3, 4],
]) // → 1

Back to index


78. Redundant Connection

Difficulty: Medium · Time: O(n · α(n)) · Space: O(n)

Find edge that can be removed to keep tree.

Approach (step by step):

  1. Union-Find; first edge connecting already-connected nodes is redundant.
js
function findRedundantConnection(edges) {
  const parent = Array.from({ length: edges.length + 1 }, (_, i) => i)
  const find = (x) => (parent[x] === x ? x : (parent[x] = find(parent[x])))
  for (const [a, b] of edges) {
    const ra = find(a),
      rb = find(b)
    if (ra === rb) return [a, b]
    parent[ra] = rb
  }
  return []
}

Hand-written trace

js
// findRedundantConnection edge [2,3] closes cycle 1-2-3

Function calls with output

js
// ── Demo calls — output shown on the right ──
findRedundantConnection([
  [1, 2],
  [1, 3],
  [2, 3],
]) // → [2,3]
findRedundantConnection([
  [1, 2],
  [2, 3],
  [3, 4],
  [1, 4],
  [1, 5],
]) // → [1,4]

Back to index


79. Accounts Merge

Difficulty: Medium · Time: O(n · k · α(n)) · Space: O(n · k)

Merge accounts sharing common emails.

Approach (step by step):

  1. Union emails in same account.
  2. Group by root; sort names per user.
js
function accountsMerge(accounts) {
  const parent = new Map()
  const find = (x) => {
    if (!parent.has(x)) parent.set(x, x)
    if (parent.get(x) !== x) parent.set(x, find(parent.get(x)))
    return parent.get(x)
  }
  const unite = (a, b) => parent.set(find(a), find(b))
  const owner = new Map()
  for (const acc of accounts) {
    const name = acc[0]
    for (let i = 1; i < acc.length; i++) {
      owner.set(acc[i], name)
      if (i > 1) unite(acc[1], acc[i])
    }
  }
  const groups = new Map()
  for (const email of owner.keys()) {
    const root = find(email)
    if (!groups.has(root)) groups.set(root, [])
    groups.get(root).push(email)
  }
  return [...groups.values()].map((emails) => {
    emails.sort()
    return [owner.get(emails[0]), ...emails]
  })
}

Hand-written trace

js
// accountsMerge union j@d j@e j@f under John

Function calls with output

js
// ── Demo calls — output shown on the right ──
accountsMerge([
  ['John', 'j@d', 'j@e'],
  ['John', 'j@e', 'j@f'],
  ['Mary', 'm@a'],
]) // → merged John emails
accountsMerge([['Gabe', 'g@g']]) // → unchanged

Back to index


Pattern 18 — DP

80. Climbing Stairs

Difficulty: Easy · Time: O(n) · Space: O(1)

Ways to climb n stairs taking 1 or 2 steps.

Approach (step by step):

  1. Fibonacci DP: ways[i] = ways[i-1] + ways[i-2].
js
function climbStairs(n) {
  if (n <= 2) return n
  let a = 1,
    b = 2
  for (let i = 3; i <= n; i++) {
    const c = a + b
    a = b
    b = c
  }
  return b
}

Hand-written trace

js
// climbStairs(3)
// ways: 1,2,3 → 3 ways

Function calls with output

js
// ── Demo calls — output shown on the right ──
climbStairs(2) // → 2
climbStairs(3) // → 3

Back to index


81. House Robber

Difficulty: Medium · Time: O(n) · Space: O(1)

Max money robbing non-adjacent houses.

Approach (step by step):

  1. dp[i] = max(dp[i-1], nums[i] + dp[i-2]).
js
function rob(nums) {
  let prev = 0,
    cur = 0
  for (const x of nums) {
    const next = Math.max(cur, prev + x)
    prev = cur
    cur = next
  }
  return cur
}

Hand-written trace

js
// rob([1,2,3,1])
// take 1+3=4 skip adjacent

Function calls with output

js
// ── Demo calls — output shown on the right ──
rob([1, 2, 3, 1]) // → 4
rob([2, 7, 9, 3, 1]) // → 12

Back to index


82. Longest Increasing Subsequence

Difficulty: Medium · Time: O(n log n) · Space: O(n)

Length of longest strictly increasing subsequence.

Approach (step by step):

  1. Patience sorting tails array; binary search placement.
js
function lengthOfLIS(arr) {
  const tails = []
  for (const x of arr) {
    let lo = 0,
      hi = tails.length
    while (lo < hi) {
      const mid = (lo + hi) >> 1
      if (tails[mid] < x) lo = mid + 1
      else hi = mid
    }
    tails[lo] = x
  }
  return tails.length
}

Hand-written trace

js
// lengthOfLIS [10,9,2,5,3,7,101,18]
// tails ends length 4 e.g. 2,3,7,18

Function calls with output

js
// ── Demo calls — output shown on the right ──
lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18]) // → 4
lengthOfLIS([0, 1, 0, 3, 2, 3]) // → 4

Back to index


83. Coin Change

Difficulty: Medium · Time: O(n · amount) · Space: O(amount)

Fewest coins to make amount, or -1.

Approach (step by step):

  1. DP array size amount+1 init infinity; dp[0]=0; relax each coin.
js
function coinChange(coins, amount) {
  const dp = Array(amount + 1).fill(Infinity)
  dp[0] = 0
  for (const c of coins) for (let a = c; a <= amount; a++) dp[a] = Math.min(dp[a], dp[a - c] + 1)
  return dp[amount] === Infinity ? -1 : dp[amount]
}

Hand-written trace

js
// coinChange amount11 coins 1,2,5 → 5+5+1 = 3 coins

Function calls with output

js
// ── Demo calls — output shown on the right ──
coinChange([1, 2, 5], 11) // → 3
coinChange([2], 3) // → -1

Back to index


84. Edit Distance

Difficulty: Medium · Time: O(m · n) · Space: O(m · n)

Minimum operations to convert word1 to word2 (insert/delete/replace).

Approach (step by step):

  1. 2D DP on prefixes; match skip, else 1 + min of three ops.
js
function minDistance(word1, word2) {
  const m = word1.length,
    n = word2.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++) {
      if (word1[i - 1] === word2[j - 1]) dp[i][j] = dp[i - 1][j - 1]
      else dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])
    }
  }
  return dp[m][n]
}

Hand-written trace

js
// minDistance horse→ros replace h→r, remove r, remove e → 3

Function calls with output

js
// ── Demo calls — output shown on the right ──
minDistance('horse', 'ros') // → 3
minDistance('intention', 'execution') // → 5

Back to index


Pattern 19 — Greedy

85. Jump Game

Difficulty: Medium · Time: O(n) · Space: O(1)

Can reach last index if arr[i] is max jump from i?

Approach (step by step):

  1. Track farthest reachable; fail if i > farthest.
js
function canJump(arr) {
  let far = 0
  for (let i = 0; i < arr.length; i++) {
    if (i > far) return false
    far = Math.max(far, i + arr[i])
    if (far >= arr.length - 1) return true
  }
  return true
}

Hand-written trace

js
// canJump [2,3,1,1,4]
// far reaches 4 at i=0 → true

Function calls with output

js
// ── Demo calls — output shown on the right ──
canJump([2, 3, 1, 1, 4]) // → true
canJump([3, 2, 1, 0, 4]) // → false

Back to index


86. Gas Station

Difficulty: Medium · Time: O(n) · Space: O(1)

Starting gas station index to complete circuit, or -1.

Approach (step by step):

  1. If total gas ≥ total cost feasible.
  2. Reset start when tank negative.
js
function canCompleteCircuit(gas, cost) {
  let tank = 0,
    start = 0
  if (gas.reduce((a, b) => a + b, 0) < cost.reduce((a, b) => a + b, 0)) return -1
  for (let i = 0; i < gas.length; i++) {
    tank += gas[i] - cost[i]
    if (tank < 0) {
      start = i + 1
      tank = 0
    }
  }
  return start
}

Hand-written trace

js
// canCompleteCircuit start resets until index3 viable circuit

Function calls with output

js
// ── Demo calls — output shown on the right ──
canCompleteCircuit([1, 2, 3, 4, 5], [3, 4, 5, 1, 2]) // → 3
canCompleteCircuit([2, 3, 4], [3, 4, 3]) // → -1

Back to index


87. Partition Labels

Difficulty: Medium · Time: O(n) · Space: O(1)

Partition string into max parts where each letter appears in at most one part.

Approach (step by step):

  1. Record last index of each char.
  2. Extend end to max last; cut when i === end.
js
function partitionLabels(s) {
  const last = new Map()
  for (let i = 0; i < s.length; i++) last.set(s[i], i)
  const out = []
  let start = 0,
    end = 0
  for (let i = 0; i < s.length; i++) {
    end = Math.max(end, last.get(s[i]))
    if (i === end) {
      out.push(end - start + 1)
      start = i + 1
    }
  }
  return out
}

Hand-written trace

js
// partitionLabels ababcbacadefegde...
// first part ends idx8 len9; next parts 7,8

Function calls with output

js
// ── Demo calls — output shown on the right ──
partitionLabels('ababcbacadefegdehijhklij') // → [9, 7, 8]
partitionLabels('eccbbbbdec') // → [10]

Back to index


Pattern 20 — Bit Manipulation

88. Single Number

Difficulty: Easy · Time: O(n) · Space: O(1)

Every element appears twice except one; find it.

Approach (step by step):

  1. XOR all numbers; pairs cancel to single.
js
function singleNumber(arr) {
  return arr.reduce((a, b) => a ^ b, 0)
}

Hand-written trace

js
// singleNumber [4,1,2,1,2]
// xor: 4^1^2^1^2 = 4

Function calls with output

js
// ── Demo calls — output shown on the right ──
singleNumber([2, 2, 1]) // → 1
singleNumber([4, 1, 2, 1, 2]) // → 4

Back to index


89. Counting Bits

Difficulty: Easy · Time: O(n) · Space: O(n)

For each i in [0,n] return number of 1 bits.

Approach (step by step):

  1. dp[i] = dp[i >> 1] + (i & 1).
js
function countBits(n) {
  const dp = Array(n + 1).fill(0)
  for (let i = 1; i <= n; i++) dp[i] = dp[i >> 1] + (i & 1)
  return dp
}

Hand-written trace

js
// countBits(5)
// dp[0..5] = [0,1,1,2,1,2]

Function calls with output

js
// ── Demo calls — output shown on the right ──
countBits(2) // → [0, 1, 1]
countBits(5) // → [0, 1, 1, 2, 1, 2]

Back to index


90. Reverse Bits

Difficulty: Easy · Time: O(1) · Space: O(1)

Reverse bits of 32-bit unsigned integer.

Approach (step by step):

  1. Shift result left; take lsb of n; shift n right 32 times.
js
function reverseBits(n) {
  let res = 0
  for (let i = 0; i < 32; i++) {
    res = (res << 1) | (n & 1)
    n >>>= 1
  }
  return res >>> 0
}

Hand-written trace

js
// reverseBits 43261596
// build reversed 32-bit → 964176192

Function calls with output

js
// ── Demo calls — output shown on the right ──
reverseBits(43261596) // → 964176192
reverseBits(2147483644) // → 1073741822

Back to index


Summary

PatternsProblemsCore techniques
2090Two pointers, sliding window, hash map, DFS/BFS, DP, greedy, bit tricks

Interview Answer

"How do you pick the right pattern in an interview?"

Clarify input/output and constraints → scan for signals (sorted array → two pointers/binary search; contiguous subarray → sliding window/prefix sum; tree/graph → DFS/BFS; optimization with overlapping subproblems → DP) → state time/space → code with edge cases.

Next reads: Array Coding Problems · Binary Search · Graphs

Share this article

XLinkedInFacebook
Kazi Rahamatullah

Written by

Kazi Rahamatullah

FullStack Developer

X / TwitterGitHubLinkedIn

Subscribe to my newsletter

Stay up to date and get notified when I share new contents.

No spam ever, unsubscribe anytime