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.
Introduction
Pattern recognition is the fastest path through coding interviews. This master sheet covers 90 curated problems across 20 patterns, each with:
- Step-by-step approach
- Full JavaScript implementation
- Hand-written execution trace
- Function calls with side output comments
Prerequisites: Big O notation · DSA Arrays
Quick index
Pattern 1 — Arrays
| # | Problem | Level | Time |
|---|---|---|---|
| 1 | Move Zeroes | Easy | O(n) |
| 2 | Product of Array Except Self | Medium | O(n) |
| 3 | Rotate Array | Medium | O(n) |
| 4 | Merge Intervals | Medium | O(n log n) |
| 5 | Insert Interval | Medium | O(n) |
| 6 | First Missing Positive | Hard | O(n) |
| 7 | Find Duplicate Number (LC 287) | Medium | O(n) |
| 8 | Find All Numbers Disappeared (LC 448) | Easy | O(n) |
| 9 | Set Mismatch (LC 645) | Easy | O(n) |
| 10 | Find All Duplicates (LC 442) | Medium | O(n) |
| 11 | Maximum Subarray Sum | Medium | O(n) |
| 12 | Best Time to Buy and Sell Stock | Easy | O(n) |
Pattern 2 — Two Pointers
| # | Problem | Level | Time |
|---|---|---|---|
| 13 | Two Sum II | Medium | O(n) |
| 14 | Container With Most Water | Medium | O(n) |
| 15 | 3Sum | Medium | O(n²) |
| 16 | Remove Duplicates from Sorted Array | Easy | O(n) |
| 17 | Valid Palindrome | Easy | O(n) |
Pattern 3 — Sliding Window
| # | Problem | Level | Time |
|---|---|---|---|
| 18 | Longest Substring Without Repeating Characters | Medium | O(n) |
| 19 | Minimum Window Substring | Hard | O(n) |
| 20 | Permutation in String | Medium | O(n) |
| 21 | Find All Anagrams in a String | Medium | O(n) |
| 22 | Maximum Average Subarray I | Easy | O(n) |
Pattern 4 — Prefix Sum
| # | Problem | Level | Time |
|---|---|---|---|
| 23 | Subarray Sum Equals K | Medium | O(n) |
| 24 | Continuous Subarray Sum | Medium | O(n) |
| 25 | Range Sum Query | Easy | O(n) build, O(1) query |
| 26 | Product of Array Except Self (Prefix) | Medium | O(n) |
Pattern 5 — Hash Map
| # | Problem | Level | Time |
|---|---|---|---|
| 27 | Two Sum | Easy | O(n) |
| 28 | Group Anagrams | Medium | O(n · k log k) |
| 29 | Isomorphic Strings | Easy | O(n) |
| 30 | Happy Number | Easy | O(log n) |
Pattern 6 — Fast Slow Pointers
| # | Problem | Level | Time |
|---|---|---|---|
| 31 | Linked List Cycle | Easy | O(n) |
| 32 | Linked List Cycle II | Medium | O(n) |
| 33 | Happy Number (Floyd) | Easy | O(log n) |
| 34 | Find Duplicate Number (Floyd) | Medium | O(n) |
Pattern 7 — Linked List
| # | Problem | Level | Time |
|---|---|---|---|
| 35 | Reverse Linked List | Easy | O(n) |
| 36 | Merge Two Sorted Lists | Easy | O(n + m) |
| 37 | Remove Nth Node From End | Medium | O(n) |
| 38 | Add Two Numbers | Medium | O(max(n,m)) |
| 39 | Copy List With Random Pointer | Medium | O(n) |
| 40 | Swap Nodes in Pairs | Medium | O(n) |
| 41 | Rotate List | Medium | O(n) |
Pattern 8 — Binary Search
| # | Problem | Level | Time |
|---|---|---|---|
| 42 | Binary Search | Easy | O(log n) |
| 43 | Search in Rotated Sorted Array | Medium | O(log n) |
| 44 | Find Peak Element | Medium | O(log n) |
| 45 | Median of Two Sorted Arrays | Hard | O(log min(n,m)) |
Pattern 9 — Stack
| # | Problem | Level | Time |
|---|---|---|---|
| 46 | Valid Parentheses | Easy | O(n) |
| 47 | Daily Temperatures | Medium | O(n) |
| 48 | Next Greater Element | Medium | O(n) |
| 49 | Largest Rectangle in Histogram | Hard | O(n) |
| 50 | 132 Pattern | Medium | O(n) |
Pattern 10 — Heap
| # | Problem | Level | Time |
|---|---|---|---|
| 51 | Top K Frequent Elements | Medium | O(n log k) |
| 52 | K Closest Points to Origin | Medium | O(n log k) |
| 53 | Find Median from Data Stream | Hard | O(log n) per add |
| 54 | Merge K Sorted Lists | Hard | O(n log k) |
Pattern 11 — Backtracking
| # | Problem | Level | Time |
|---|---|---|---|
| 55 | Subsets | Medium | O(n · 2^n) |
| 56 | Permutations | Medium | O(n · n!) |
| 57 | Combination Sum | Medium | O(2^n) |
| 58 | Word Search | Medium | O(m · n · 4^L) |
| 59 | N-Queens | Hard | O(n!) |
Pattern 12 — Trees DFS
| # | Problem | Level | Time |
|---|---|---|---|
| 60 | Maximum Depth of Binary Tree | Easy | O(n) |
| 61 | Path Sum | Easy | O(n) |
| 62 | Diameter of Binary Tree | Easy | O(n) |
| 63 | Lowest Common Ancestor | Medium | O(n) |
Pattern 13 — Trees BFS
| # | Problem | Level | Time |
|---|---|---|---|
| 64 | Binary Tree Level Order Traversal | Medium | O(n) |
| 65 | Binary Tree Right Side View | Medium | O(n) |
| 66 | Minimum Depth of Binary Tree | Easy | O(n) |
Pattern 14 — BST
| # | Problem | Level | Time |
|---|---|---|---|
| 67 | Validate BST | Medium | O(n) |
| 68 | Kth Smallest Element in BST | Medium | O(h + k) |
| 69 | Convert Sorted Array to BST | Easy | O(n) |
Pattern 15 — Trie
| # | Problem | Level | Time |
|---|---|---|---|
| 70 | Implement Trie | Medium | O(m) per op |
| 71 | Word Search II | Hard | O(m · n · 4^L) |
| 72 | Design Add and Search Words | Medium | O(m) add, O(26^dots) search |
Pattern 16 — Graphs
| # | Problem | Level | Time |
|---|---|---|---|
| 73 | Number of Islands | Medium | O(m · n) |
| 74 | Clone Graph | Medium | O(V + E) |
| 75 | Course Schedule | Medium | O(V + E) |
| 76 | Pacific Atlantic Water Flow | Medium | O(m · n) |
Pattern 17 — Union Find
| # | Problem | Level | Time |
|---|---|---|---|
| 77 | Number of Connected Components | Medium | O(n + e · α(n)) |
| 78 | Redundant Connection | Medium | O(n · α(n)) |
| 79 | Accounts Merge | Medium | O(n · k · α(n)) |
Pattern 18 — DP
| # | Problem | Level | Time |
|---|---|---|---|
| 80 | Climbing Stairs | Easy | O(n) |
| 81 | House Robber | Medium | O(n) |
| 82 | Longest Increasing Subsequence | Medium | O(n log n) |
| 83 | Coin Change | Medium | O(n · amount) |
| 84 | Edit Distance | Medium | O(m · n) |
Pattern 19 — Greedy
| # | Problem | Level | Time |
|---|---|---|---|
| 85 | Jump Game | Medium | O(n) |
| 86 | Gas Station | Medium | O(n) |
| 87 | Partition Labels | Medium | O(n) |
Pattern 20 — Bit Manipulation
| # | Problem | Level | Time |
|---|---|---|---|
| 88 | Single Number | Easy | O(n) |
| 89 | Counting Bits | Easy | O(n) |
| 90 | Reverse Bits | Easy | O(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):
- Maintain write pointer for next non-zero slot.
- Scan: copy non-zero values forward.
- Fill remaining indices with zero.
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
// 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
// ── 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]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):
- First pass: store prefix product at each index.
- Second pass: multiply by suffix running product right-to-left.
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
// 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
// ── 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]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):
- Normalize k %= n.
- Reverse whole array, then reverse first k and last n-k parts.
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
// 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
// ── 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]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):
- Sort by start.
- If current overlaps last, extend end; else push new interval.
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
// 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
// ── 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]]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):
- Add intervals ending before new.
- Merge overlapping.
- Append rest.
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
// insertInterval([[1,3],[6,9]], [2,5])
// [1,3] overlaps → merged [1,5]
// [6,9] after → [[1,5],[6,9]]Function calls with output
// ── 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]]6. First Missing Positive
Difficulty: Hard · Time: O(n) · Space: O(1)
Find smallest missing positive integer in unsorted array.
Approach (step by step):
- Cyclic sort: place value v at index v-1 when 1 ≤ v ≤ n.
- Scan for first index where arr[i] ≠ i+1.
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
// firstMissingPositive([3,4,-1,1])
// place 1→idx0, 3→idx2, 4→idx3 → [1,-1,3,4]
// index1 arr[1]=-1 ≠ 2 → return 2Function calls with output
// ── Demo calls — output shown on the right ──
firstMissingPositive([3, 4, -1, 1]) // → 2
firstMissingPositive([1, 2, 0]) // → 37. 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):
- Treat as linked list cycle: index → arr[index].
- Floyd cycle detection; duplicate is cycle entry.
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
// 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 → 2Function calls with output
// ── Demo calls — output shown on the right ──
findDuplicate([1, 3, 4, 2, 2]) // → 2
findDuplicate([3, 1, 3, 4, 2]) // → 38. 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):
- Mark visited index abs(arr[i])-1 by negating.
- Collect indices still positive +1.
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
// 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,6Function calls with output
// ── Demo calls — output shown on the right ──
findDisappearedNumbers([4, 3, 2, 7, 8, 2, 3, 1]) // → [5, 6]
findDisappearedNumbers([1, 1]) // → [2]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):
- XOR/index marking or math on sum and sum of squares.
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
// 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
// ── Demo calls — output shown on the right ──
findErrorNums([1, 2, 2, 4]) // → [2, 3]
findErrorNums([1, 1]) // → [1, 2]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):
- Same index marking: if arr[idx] already negative, idx+1 is duplicate.
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
// findDuplicates([4,3,2,7,8,2,3,1])
// second visit to idx1 → 2, idx2 → 3
// result → [2,3]Function calls with output
// ── Demo calls — output shown on the right ──
findDuplicates([4, 3, 2, 7, 8, 2, 3, 1]) // → [2, 3]
findDuplicates([1, 1, 2]) // → [1]11. Maximum Subarray Sum
Difficulty: Medium · Time: O(n) · Space: O(1)
Find contiguous subarray with largest sum (Kadane).
Approach (step by step):
- Extend running sum or restart at current element.
- Track global maximum.
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
// 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
// ── Demo calls — output shown on the right ──
maxSubarraySum([-2, 1, -3, 4, -1, 2, 1, -5, 4]) // → 6
maxSubarraySum([5, 4, -1, 7, 8]) // → 2312. 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):
- Track minimum price so far.
- Update max profit at each day.
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
// maxProfit([7,1,5,3,6,4])
// minP:7,1,1,1,1 profit:0,4,2,5
// buy@1 sell@6 → 5Function calls with output
// ── Demo calls — output shown on the right ──
maxProfit([7, 1, 5, 3, 6, 4]) // → 5
maxProfit([7, 6, 4, 3, 1]) // → 0Pattern 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):
- Left at start, right at end.
- If sum too small move left++; too big move right--.
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
// 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
// ── Demo calls — output shown on the right ──
twoSumII([2, 7, 11, 15], 9) // → [1, 2]
twoSumII([2, 3, 4], 6) // → [1, 3]14. Container With Most Water
Difficulty: Medium · Time: O(n) · Space: O(1)
Max area between two vertical lines.
Approach (step by step):
- Two pointers at ends.
- Move shorter side inward; track max area.
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
// maxArea([1,8,6,2,5,4,8,3,7])
// best at l=1,r=8: min(8,7)*7=49Function calls with output
// ── Demo calls — output shown on the right ──
maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7]) // → 49
maxArea([1, 1]) // → 115. 3Sum
Difficulty: Medium · Time: O(n²) · Space: O(1)
Return all unique triplets summing to zero.
Approach (step by step):
- Sort array.
- Fix i; two-pointer scan for pairs with sum -arr[i].
- Skip duplicates.
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
// 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
// ── 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]]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):
- Slow pointer marks last unique; fast scans and copies new values.
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
// removeDuplicates([1,1,2,2,3])
// w=1: copy 2→w=2, copy 3→w=3
// return 3Function calls with output
// ── Demo calls — output shown on the right ──
removeDuplicates([1, 1, 2, 2, 3]) // → 3 (array starts [1,2,3])
removeDuplicates([1, 2, 3]) // → 317. Valid Palindrome
Difficulty: Easy · Time: O(n) · Space: O(1)
Check if string is palindrome considering alphanumeric only, case-insensitive.
Approach (step by step):
- Two pointers skip non-alnum.
- Compare lowercased chars; move inward.
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
// isPalindrome("A man, a plan, a canal: Panama")
// compare a↔a, m↔m, ... all match → trueFunction calls with output
// ── Demo calls — output shown on the right ──
isPalindrome('A man, a plan, a canal: Panama') // → true
isPalindrome('race a car') // → falsePattern 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):
- Expand right; shrink left while duplicate in window.
- Track char last index and max length.
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
// lengthOfLongestSubstring("abcabcbb")
// window "abc" len=3 best; shrink at repeat b,c
// best stays 3Function calls with output
// ── Demo calls — output shown on the right ──
lengthOfLongestSubstring('abcabcbb') // → 3
lengthOfLongestSubstring('bbbbb') // → 119. Minimum Window Substring
Difficulty: Hard · Time: O(n) · Space: O(1)
Smallest substring of s containing all characters of t.
Approach (step by step):
- Expand until valid; shrink from left while valid.
- Track need/have counts.
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
// minWindow("ADOBECODEBANC","ABC")
// valid at ADOBEC, shrink → BANC len=4 bestFunction calls with output
// ── Demo calls — output shown on the right ──
minWindow('ADOBECODEBANC', 'ABC') // → "BANC"
minWindow('a', 'a') // → "a"20. Permutation in String
Difficulty: Medium · Time: O(n) · Space: O(1)
Return true if s2 contains permutation of s1.
Approach (step by step):
- Fixed window size |s1|.
- Compare frequency maps; slide one char at a time.
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
// checkInclusion("ab","eidbaooo")
// window "dba" matches ab permutation → trueFunction calls with output
// ── Demo calls — output shown on the right ──
checkInclusion('ab', 'eidbaooo') // → true
checkInclusion('ab', 'eidboaoo') // → false21. 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):
- Sliding window of len(p) with freq diff counter.
- When diff zero, record start index.
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
// findAnagrams("cbaebabacd","abc")
// index0 "cba", index6 "bac" → [0,6]Function calls with output
// ── Demo calls — output shown on the right ──
findAnagrams('cbaebabacd', 'abc') // → [0, 6]
findAnagrams('abab', 'ab') // → [0, 1, 2]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):
- Sum first k elements.
- Slide: add next, subtract leaving; track max sum.
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
// findMaxAverage([1,12,-5,-6,50,3], 4)
// sums: 2, 51 best → 51/4=12.75Function calls with output
// ── Demo calls — output shown on the right ──
findMaxAverage([1, 12, -5, -6, 50, 3], 4) // → 12.75
findMaxAverage([5], 1) // → 5Pattern 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):
- Prefix sum + hash map of prefix frequency.
- Add count of prefix sum - k at each step.
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
// subarraySum([1,1,1], 2)
// prefix 1→0, 2→1 hit, 3→1 hit → 2Function calls with output
// ── Demo calls — output shown on the right ──
subarraySum([1, 1, 1], 2) // → 2
subarraySum([1, 2, 3], 3) // → 224. 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):
- Store prefix mod k in map with index.
- If same mod seen ≥2 apart, return true.
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
// checkSubarraySum([23,2,4,6,7], 6)
// mod 2 at idx1 and idx4 len≥2 → trueFunction calls with output
// ── Demo calls — output shown on the right ──
checkSubarraySum([23, 2, 4, 6, 7], 6) // → true
checkSubarraySum([23, 2, 6, 4, 7], 6) // → true25. 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):
- Build prefix array where prefix[i] = sum of nums[0..i-1].
- Query = prefix[right+1] - prefix[left].
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
// NumArray([-2,0,3,-4,2,-1])
// prefix [0,-2,-2,1,-3,-1,-2]
// sumRange(0,2) = prefix[3]-prefix[0] = 1Function calls with output
// ── 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) // → -126. Product of Array Except Self (Prefix)
Difficulty: Medium · Time: O(n) · Space: O(1)
Prefix/suffix product approach without division.
Approach (step by step):
- Left pass stores prefix products.
- Right pass multiplies suffix into output.
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
// productExceptSelfPrefix([1,2,3,4])
// left out=[1,1,2,6]; right multiply → [24,12,8,6]Function calls with output
// ── 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]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):
- Map value → index.
- For each x check if target-x seen.
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
// twoSum([2,7,11,15], 9)
// i=0 map{2:0}; i=1 need=2 found → [0,1]Function calls with output
// ── Demo calls — output shown on the right ──
twoSum([2, 7, 11, 15], 9) // → [0, 1]
twoSum([3, 2, 4], 6) // → [1, 2]28. Group Anagrams
Difficulty: Medium · Time: O(n · k log k) · Space: O(n · k)
Group strings that are anagrams.
Approach (step by step):
- Sort each string as key or use char count signature.
- Bucket into map.
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
// groupAnagrams(["eat","tea","tan","ate","nat","bat"])
// keys aet→3 words, ant→2, abt→batFunction calls with output
// ── Demo calls — output shown on the right ──
groupAnagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat']) // → [["eat","tea","ate"],["tan","nat"],["bat"]]
groupAnagrams(['']) // → [[""]]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):
- Two maps s→t and t→s must stay consistent.
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
// isIsomorphic("egg","add")
// e→a, g→d consistent → trueFunction calls with output
// ── Demo calls — output shown on the right ──
isIsomorphic('egg', 'add') // → true
isIsomorphic('foo', 'bar') // → false30. 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):
- Use set to detect cycle.
- Return true if reach 1.
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
undefinedFunction calls with output
// ── Demo calls — output shown on the right ──
isHappy(19) // → true
isHappy(2) // → falsePattern 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):
- Slow moves 1, fast moves 2.
- If they meet, cycle exists.
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
// hasCycle 3→2→0→-4→(back to 2)
// slow:3,2,0,-4,2 fast:3,0,2,0 meet → trueFunction calls with output
// ── Demo calls — output shown on the right ──
hasCycle(/* 3→2→0→-4↘ */) // → true
hasCycle(/* 1→2 */) // → false32. Linked List Cycle II
Difficulty: Medium · Time: O(n) · Space: O(1)
Return node where cycle begins, or null.
Approach (step by step):
- Floyd detect cycle.
- Reset slow to head; advance both 1 step to find entry.
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
// detectCycle entry at node val=2
// meet inside cycle; reset slow=head → both reach node 2Function calls with output
// ── Demo calls — output shown on the right ──
detectCycle(/* cycle entry at 2 */) // → node 2
detectCycle(/* no cycle */) // → null33. Happy Number (Floyd)
Difficulty: Easy · Time: O(log n) · Space: O(1)
Happy number using cycle detection instead of set.
Approach (step by step):
- Next function on digits.
- Floyd on sequence; happy iff reaches 1.
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
// isHappyFloyd(19)
// slow/fast on digit-square chain → reaches 1Function calls with output
// ── Demo calls — output shown on the right ──
isHappyFloyd(19) // → true
isHappyFloyd(2) // → false34. 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):
- Map i → arr[i] as next pointer.
- Cycle entry is duplicate value.
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
// findDuplicateFloyd([1,3,4,2,2])
// index graph cycle entry → 2Function calls with output
// ── Demo calls — output shown on the right ──
findDuplicateFloyd([1, 3, 4, 2, 2]) // → 2
findDuplicateFloyd([3, 1, 3, 4, 2]) // → 3Pattern 7 — Linked List
35. Reverse Linked List
Difficulty: Easy · Time: O(n) · Space: O(1)
Reverse singly linked list iteratively.
Approach (step by step):
- prev=null; swap next pointers while advancing.
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
// reverseList 1→2→3
// prev: null,1,2 head ends 3→2→1Function calls with output
// ── Demo calls — output shown on the right ──
reverseList(/* 1→2→3→4→5 */) // → 5→4→3→2→1
reverseList(/* 1→2 */) // → 2→136. Merge Two Sorted Lists
Difficulty: Easy · Time: O(n + m) · Space: O(1)
Merge two sorted linked lists.
Approach (step by step):
- Dummy head; attach smaller node; advance pointer.
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
// merge 1→2→4 and 1→3→4
// pick 1,1,2,3,4,4 → merged listFunction calls with output
// ── Demo calls — output shown on the right ──
mergeTwoLists(/*1→2→4*/, /*1→3→4*/) // → 1→1→2→3→4→4
mergeTwoLists(null, /*0*/) // → 037. 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):
- Fast pointer n ahead.
- Move both until fast at end; delete slow.next.
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
// removeNthFromEnd 1→2→3→4→5 n=2
// fast stops at null; slow before 4 → skip 5Function calls with output
// ── Demo calls — output shown on the right ──
removeNthFromEnd(/*1→2→3→4→5*/, 2) // → 1→2→3→5
removeNthFromEnd(/*1*/, 1) // → null38. 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):
- Add digits + carry; build result list.
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
// addTwoNumbers 342+465=807 → 7→0→8Function calls with output
// ── Demo calls — output shown on the right ──
addTwoNumbers(/*2→4→3*/, /*5→6→4*/) // → 7→0→8
addTwoNumbers(/*0*/, /*0*/) // → 039. Copy List With Random Pointer
Difficulty: Medium · Time: O(n) · Space: O(n)
Deep copy linked list with random pointers.
Approach (step by step):
- Map old node → new node.
- Second pass wire next and random.
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
// copyRandomList: clone each node, map old→new, wire linksFunction calls with output
// ── Demo calls — output shown on the right ──
copyRandomList(/* [[7,null],[13,0],[11,4],[10,2],[1,0]] */) // → deep copy
copyRandomList(null) // → null40. Swap Nodes in Pairs
Difficulty: Medium · Time: O(n) · Space: O(1)
Swap every two adjacent nodes.
Approach (step by step):
- Dummy before head; reverse pairs iteratively.
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
// swapPairs 1→2→3→4
// swap(1,2) then (3,4) → 2→1→4→3Function calls with output
// ── Demo calls — output shown on the right ──
swapPairs(/*1→2→3→4*/) // → 2→1→4→3
swapPairs(/*1→2→3*/) // → 2→1→341. Rotate List
Difficulty: Medium · Time: O(n) · Space: O(1)
Rotate list to the right by k places.
Approach (step by step):
- Find length; k%=n; connect tail to head; break at new tail.
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
// rotateRight 1→2→3→4→5 k=2 len=5
// tail→head; break before node4 → 4→5→1→2→3Function calls with output
// ── 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→1Pattern 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):
- lo/hi bounds; mid compare; narrow half.
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
// binarySearch([-1,0,3,5,9,12], 9)
// lo,hi: 0,5 mid=2 val3<9 → lo=3
// 3,5 mid=4 val9 → return 4Function calls with output
// ── Demo calls — output shown on the right ──
binarySearch([-1, 0, 3, 5, 9, 12], 9) // → 4
binarySearch([-1, 0, 3, 5, 9, 12], 2) // → -143. 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):
- Identify sorted half; check if target in range; narrow.
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
// searchRotated([4,5,6,7,0,1,2], 0)
// left half sorted; 0 not in [4,7) → lo=mid+1 → find idx4Function calls with output
// ── 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) // → -144. 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):
- If
mid < mid+1, peak on right; else on left.
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
// findPeakElement([1,2,3,1])
// mid=1 2<3 → lo=2; lo=hi=2 peak idx2 val3Function calls with output
// ── Demo calls — output shown on the right ──
findPeakElement([1, 2, 3, 1]) // → 2
findPeakElement([1, 2, 1, 3, 5, 6, 4]) // → 545. 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):
- Binary search partition on smaller array.
- Ensure left max ≤ right min.
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
// findMedianSortedArrays([1,3],[2])
// partition i=1,j=0 → left [1] right [2,3] median=2Function calls with output
// ── Demo calls — output shown on the right ──
findMedianSortedArrays([1, 3], [2]) // → 2
findMedianSortedArrays([1, 2], [3, 4]) // → 2.5Pattern 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):
- Stack opening brackets; pop on matching close.
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
// isValid("()[]{}")
// push ( [ { ; pop matches → empty stack trueFunction calls with output
// ── Demo calls — output shown on the right ──
isValid('()[]{}') // → true
isValid('(]') // → false47. Daily Temperatures
Difficulty: Medium · Time: O(n) · Space: O(n)
Days until warmer temperature for each day.
Approach (step by step):
- Monotonic decreasing stack of indices.
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
// dailyTemperatures([73,74,75,...])
// stack resolves warmer days → wait countsFunction calls with output
// ── 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]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):
- Monotonic stack; pop smaller when larger seen.
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
// nextGreaterElements([2,1,2,4,3])
// idx0→4 idx1→2 idx2→4 idx3,4→-1Function calls with output
// ── 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]49. Largest Rectangle in Histogram
Difficulty: Hard · Time: O(n) · Space: O(n)
Largest rectangle area in histogram.
Approach (step by step):
- Stack increasing bars; compute width on pop.
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
// largestRectangleArea([2,1,5,6,2,3])
// max area 5*2=10 between bars height5Function calls with output
// ── Demo calls — output shown on the right ──
largestRectangleArea([2, 1, 5, 6, 2, 3]) // → 10
largestRectangleArea([2, 4]) // → 450. 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):
- Monotonic stack from right; track best third (ak).
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
// find132pattern([3,1,4,2])
// i=0 val3: ak becomes 2 from stack → 1<2 pattern trueFunction calls with output
// ── Demo calls — output shown on the right ──
find132pattern([3, 1, 4, 2]) // → true
find132pattern([1, 2, 3, 4]) // → falsePattern 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):
- Count frequencies.
- Min-heap of size k by frequency.
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
// topKFrequent([1,1,1,2,2,3], 2)
// freq 1:3 2:2 3:1 → top [1,2]Function calls with output
// ── Demo calls — output shown on the right ──
topKFrequent([1, 1, 1, 2, 2, 3], 2) // → [1, 2]
topKFrequent([1], 1) // → [1]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):
- Max-heap by distance or quickselect.
- Keep k smallest distances.
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
// kClosest([[1,3],[-2,2]], 1)
// dist 10 vs 8 → [-2,2] closerFunction calls with output
// ── 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]]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):
- Two heaps: max-heap lower half, min-heap upper half.
- Balance sizes and read tops.
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
// add 1,2 → lo=[1,2]? balanced lo=[2] hi=[1] median 1.5
// add 3 → median 2Function calls with output
// ── 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()) // → 254. Merge K Sorted Lists
Difficulty: Hard · Time: O(n log k) · Space: O(k)
Merge k sorted linked lists.
Approach (step by step):
- Min-heap of list heads by value.
- Pop, attach, push next.
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
// mergeKLists three lists → pop smallest heads in orderFunction calls with output
// ── 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([]) // → nullPattern 11 — Backtracking
55. Subsets
Difficulty: Medium · Time: O(n · 2^n) · Space: O(n)
Return all subsets of distinct integers.
Approach (step by step):
- Start empty subset; at each index choose include or skip.
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
// subsets([1,2,3])
// add 1, then 2, then 3 to prior subsets → 8 subsetsFunction calls with output
// ── 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]]56. Permutations
Difficulty: Medium · Time: O(n · n!) · Space: O(n)
Return all permutations of distinct nums.
Approach (step by step):
- Backtrack with used array; swap or choose from remaining.
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
// permute([1,2,3])
// dfs builds [1,2,3],[1,3,2],[2,1,3]...Function calls with output
// ── Demo calls — output shown on the right ──
permute([1, 2, 3]) // → 6 permutations
permute([0, 1]) // → [[0,1],[1,0]]57. Combination Sum
Difficulty: Medium · Time: O(2^n) · Space: O(target)
Combinations summing to target; reuse candidates allowed.
Approach (step by step):
- Sort; recurse from index allowing reuse.
- Backtrack on exceed.
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
// combinationSum([2,3,6,7], 7)
// paths 2+2+3 and 7Function calls with output
// ── 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]]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):
- DFS from each cell; mark visited; backtrack.
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
// exist board ABCCED path from A→B→C→C→E→DFunction calls with output
// ── 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',
) // → false59. N-Queens
Difficulty: Hard · Time: O(n!) · Space: O(n²)
Return all distinct n-queens board configurations.
Approach (step by step):
- Place row by row; track cols and diagonals.
- Backtrack on conflict.
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
// solveNQueens(4)
// place Q rows 0..3 avoiding attacks → 2 solutionsFunction calls with output
// ── Demo calls — output shown on the right ──
solveNQueens(4).length // → 2
solveNQueens(1) // → [["Q"]]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):
- Base null → 0.
- Return 1 + max(left, right).
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
// maxDepth root3 left9 depth1 right20 depth2+1=3Function calls with output
// ── Demo calls — output shown on the right ──
maxDepth(/* [3,9,20,null,null,15,7] */) // → 3
maxDepth(/* [1,null,2] */) // → 261. Path Sum
Difficulty: Easy · Time: O(n) · Space: O(h)
True if root-to-leaf path sums to target.
Approach (step by step):
- Subtract node val from target at each step.
- Leaf check remaining === 0.
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
// hasPathSum target22 path 5→4→11→2Function calls with output
// ── 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 */) // → true62. Diameter of Binary Tree
Difficulty: Easy · Time: O(n) · Space: O(h)
Longest path between any two nodes (edges count).
Approach (step by step):
- Post-order height; update best with left+right heights.
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
// diameter tree 1-2-4 path length 3 edges via 4-2-1-3Function calls with output
// ── Demo calls — output shown on the right ──
diameterOfBinaryTree(/* [1,2,3,4,5] */) // → 3
diameterOfBinaryTree(/* [1,2] */) // → 163. Lowest Common Ancestor
Difficulty: Medium · Time: O(n) · Space: O(h)
LCA of two nodes in binary tree.
Approach (step by step):
- If node is p or q return it.
- Recurse both sides; if both non-null current is LCA.
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
// LCA(5,1) in tree → node3 splits subtrees containing bothFunction calls with output
// ── Demo calls — output shown on the right ──
lowestCommonAncestor(/* tree */, node5, node1) // → 3
lowestCommonAncestor(/* tree */, node5, node4) // → 5Pattern 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):
- BFS queue; process level size at a time.
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
// levelOrder → [[3],[9,20],[15,7]]Function calls with output
// ── Demo calls — output shown on the right ──
levelOrder(/* [3,9,20,null,null,15,7] */) // → [[3],[9,20],[15,7]]
levelOrder(/* [1] */) // → [[1]]65. Binary Tree Right Side View
Difficulty: Medium · Time: O(n) · Space: O(n)
Values visible from right side.
Approach (step by step):
- BFS; take last node of each level.
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
// rightSideView levels last: 1, 3, 4Function calls with output
// ── Demo calls — output shown on the right ──
rightSideView(/* [1,2,3,null,5,null,4] */) // → [1,3,4]
rightSideView(/* [1,null,3] */) // → [1,3]66. Minimum Depth of Binary Tree
Difficulty: Easy · Time: O(n) · Space: O(n)
Minimum root-to-leaf depth.
Approach (step by step):
- BFS until first leaf; return depth.
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
// minDepth BFS hits leaf 9 at depth 2Function calls with output
// ── Demo calls — output shown on the right ──
minDepth(/* [3,9,20,null,null,15,7] */) // → 2
minDepth(/* [2,null,3,null,4] */) // → 3Pattern 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):
- Pass min/max bounds down;
left < node< right.
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
// isValidBST [5,1,4,...] node4 < min5 on right subtree → falseFunction calls with output
// ── Demo calls — output shown on the right ──
isValidBST(/* [2,1,3] */) // → true
isValidBST(/* [5,1,4,null,null,3,6] */) // → false68. Kth Smallest Element in BST
Difficulty: Medium · Time: O(h + k) · Space: O(h)
Return kth smallest value in BST.
Approach (step by step):
- Inorder traversal; count nodes.
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
// kthSmallest inorder 1,2,3,4 k=3 → 3Function calls with output
// ── 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) // → 369. Convert Sorted Array to BST
Difficulty: Easy · Time: O(n) · Space: O(n)
Build height-balanced BST from sorted array.
Approach (step by step):
- Pick mid as root; recurse on left and right halves.
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
// sortedArrayToBST [-10,-3,0,5,9]
// root 0 left [-10,-3] right [5,9]Function calls with output
// ── Demo calls — output shown on the right ──
sortedArrayToBST([-10, -3, 0, 5, 9]) // → balanced BST
sortedArrayToBST([1, 3]) // → root 3 left 1Pattern 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):
- Node map of children; end flag on word completion.
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
// Trie insert apple → search apple true, app false, startsWith app trueFunction calls with output
// ── Demo calls — output shown on the right ──
;((t = new Trie()),
t.insert('apple'),
t.search(
'apple',
)) // → true
(t.search('app'), t.startsWith('app')) // → false, true71. 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):
- Build trie of words.
- DFS board pruning by trie paths.
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
// findWords board → DFS trie finds eat, oathFunction calls with output
// ── 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'],
) // → []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):
- Trie insert; search DFS on dot branches.
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
// WordDictionary .ad matches bad via wildcard DFSFunction calls with output
// ── Demo calls — output shown on the right ──
;((d = new WordDictionary()),
d.addWord('bad'),
d.search(
'bad',
)) // → true
(d.search('.ad'), d.search('b..')) // → true, truePattern 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):
- Scan grid; DFS/BFS flood fill each unseen land cell.
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
// numIslands 4x5 grid → flood fill 1 islandFunction calls with output
// ── 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'],
]) // → 374. Clone Graph
Difficulty: Medium · Time: O(V + E) · Space: O(V)
Deep copy connected undirected graph.
Approach (step by step):
- Map old node → clone; BFS/DFS copy neighbors.
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
// cloneGraph BFS map each node → clone neighborsFunction calls with output
// ── Demo calls — output shown on the right ──
cloneGraph(/* adj list [[2,4],[1,3],[2,4],[1,3]] */) // → deep copy
cloneGraph(null) // → null75. Course Schedule
Difficulty: Medium · Time: O(V + E) · Space: O(V + E)
Can finish all courses given prerequisites?
Approach (step by step):
- Build graph; Kahn topological sort or DFS cycle detect.
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
// canFinish 2 courses [[1,0]] topo order 0→1 → trueFunction calls with output
// ── Demo calls — output shown on the right ──
canFinish(2, [[1, 0]]) // → true
canFinish(2, [
[1, 0],
[0, 1],
]) // → false76. 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):
- DFS from ocean borders inward against height.
- Intersect reachable sets.
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
// pacificAtlantic reverse DFS from both oceans → intersection cellsFunction calls with output
// ── 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]]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):
- Union-Find: union edges; count distinct roots.
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
// countComponents n=5 edges 0-1-2 and 3-4 → 2 componentsFunction calls with output
// ── 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],
]) // → 178. Redundant Connection
Difficulty: Medium · Time: O(n · α(n)) · Space: O(n)
Find edge that can be removed to keep tree.
Approach (step by step):
- Union-Find; first edge connecting already-connected nodes is redundant.
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
// findRedundantConnection edge [2,3] closes cycle 1-2-3Function calls with output
// ── 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]79. Accounts Merge
Difficulty: Medium · Time: O(n · k · α(n)) · Space: O(n · k)
Merge accounts sharing common emails.
Approach (step by step):
- Union emails in same account.
- Group by root; sort names per user.
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
// accountsMerge union j@d j@e j@f under JohnFunction calls with output
// ── 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']]) // → unchangedPattern 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):
- Fibonacci DP: ways[i] = ways[i-1] + ways[i-2].
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
// climbStairs(3)
// ways: 1,2,3 → 3 waysFunction calls with output
// ── Demo calls — output shown on the right ──
climbStairs(2) // → 2
climbStairs(3) // → 381. House Robber
Difficulty: Medium · Time: O(n) · Space: O(1)
Max money robbing non-adjacent houses.
Approach (step by step):
- dp[i] = max(dp[i-1], nums[i] + dp[i-2]).
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
// rob([1,2,3,1])
// take 1+3=4 skip adjacentFunction calls with output
// ── Demo calls — output shown on the right ──
rob([1, 2, 3, 1]) // → 4
rob([2, 7, 9, 3, 1]) // → 1282. Longest Increasing Subsequence
Difficulty: Medium · Time: O(n log n) · Space: O(n)
Length of longest strictly increasing subsequence.
Approach (step by step):
- Patience sorting tails array; binary search placement.
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
// lengthOfLIS [10,9,2,5,3,7,101,18]
// tails ends length 4 e.g. 2,3,7,18Function calls with output
// ── Demo calls — output shown on the right ──
lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18]) // → 4
lengthOfLIS([0, 1, 0, 3, 2, 3]) // → 483. Coin Change
Difficulty: Medium · Time: O(n · amount) · Space: O(amount)
Fewest coins to make amount, or -1.
Approach (step by step):
- DP array size amount+1 init infinity; dp[0]=0; relax each coin.
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
// coinChange amount11 coins 1,2,5 → 5+5+1 = 3 coinsFunction calls with output
// ── Demo calls — output shown on the right ──
coinChange([1, 2, 5], 11) // → 3
coinChange([2], 3) // → -184. 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):
- 2D DP on prefixes; match skip, else 1 + min of three ops.
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
// minDistance horse→ros replace h→r, remove r, remove e → 3Function calls with output
// ── Demo calls — output shown on the right ──
minDistance('horse', 'ros') // → 3
minDistance('intention', 'execution') // → 5Pattern 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):
- Track farthest reachable; fail if
i > farthest.
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
// canJump [2,3,1,1,4]
// far reaches 4 at i=0 → trueFunction calls with output
// ── Demo calls — output shown on the right ──
canJump([2, 3, 1, 1, 4]) // → true
canJump([3, 2, 1, 0, 4]) // → false86. Gas Station
Difficulty: Medium · Time: O(n) · Space: O(1)
Starting gas station index to complete circuit, or -1.
Approach (step by step):
- If total gas ≥ total cost feasible.
- Reset start when tank negative.
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
// canCompleteCircuit start resets until index3 viable circuitFunction calls with output
// ── 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]) // → -187. 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):
- Record last index of each char.
- Extend end to max last; cut when i === end.
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
// partitionLabels ababcbacadefegde...
// first part ends idx8 len9; next parts 7,8Function calls with output
// ── Demo calls — output shown on the right ──
partitionLabels('ababcbacadefegdehijhklij') // → [9, 7, 8]
partitionLabels('eccbbbbdec') // → [10]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):
- XOR all numbers; pairs cancel to single.
function singleNumber(arr) {
return arr.reduce((a, b) => a ^ b, 0)
}Hand-written trace
// singleNumber [4,1,2,1,2]
// xor: 4^1^2^1^2 = 4Function calls with output
// ── Demo calls — output shown on the right ──
singleNumber([2, 2, 1]) // → 1
singleNumber([4, 1, 2, 1, 2]) // → 489. 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):
- dp[i] = dp[i >> 1] + (i & 1).
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
// countBits(5)
// dp[0..5] = [0,1,1,2,1,2]Function calls with output
// ── Demo calls — output shown on the right ──
countBits(2) // → [0, 1, 1]
countBits(5) // → [0, 1, 1, 2, 1, 2]90. Reverse Bits
Difficulty: Easy · Time: O(1) · Space: O(1)
Reverse bits of 32-bit unsigned integer.
Approach (step by step):
- Shift result left; take lsb of n; shift n right 32 times.
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
// reverseBits 43261596
// build reversed 32-bit → 964176192Function calls with output
// ── Demo calls — output shown on the right ──
reverseBits(43261596) // → 964176192
reverseBits(2147483644) // → 1073741822Summary
| Patterns | Problems | Core techniques |
|---|---|---|
| 20 | 90 | Two pointers, sliding window, hash map, DFS/BFS, DP, greedy, bit tricks |
Next reads: Array Coding Problems · Binary Search · Graphs
Subscribe to my newsletter
Stay up to date and get notified when I share new contents.
No spam ever, unsubscribe anytime