JB logo

Command Palette

Search for a command to run...

yOUTUBE
LeetCode · Beginner → FAANG-ready

Crack the Coding Interview in 100 Hours

The full roadmap, start to finish: 18 patterns that cover the vast majority of FAANG interview questions — each taught with the signal that tells you to reach for it, a reusable template, and signature problems solved in JavaScript and Go. Every solution here was run through node and go before publishing.

How to use this page: work top to bottom. For each pattern — read the recognition box, learn the template in your language, then solve both signature problems from a blank editor. Then drill that node's "More Exercises" on NeetCode or roadmap.sh. The single rule that matters: re-solve every problem cold three days later.
Pattern 00~8 hours

Foundations: the plan, the language, the mindset

Before a single pattern: how to spend 100 hours, the interview stdlib for JavaScript and Go side by side, Big-O without the hand-waving, and how to actually behave in the room.

The 100-hour plan

A hundred hours is enough to walk into a FAANG loop prepared — but only if it's structured. Grinding random problems plateaus fast. This is the schedule this page is built around: learn a pattern, drill its signature problems in both languages, move on. Roughly two focused hours a day gets you through in seven weeks.

BlockHoursWhat you drill
0 · Foundations8hBig-O, your language's interview stdlib (arrays, maps, sets, heaps, sorting), and how to talk while you code.
1 · Arrays & Hashing8hThe bread and butter. Hash maps turn O(n²) scans into O(n). Two Sum, Group Anagrams, Top K.
2 · Two Pointers + Sliding Window10hLinear scans over sorted arrays and substrings. 3Sum, Container With Most Water, Longest Substring.
3 · Stacks + Binary Search10hMonotonic stacks and searching on a sorted range — or on the answer itself. Valid Parentheses, Koko.
4 · Linked Lists + Trees14hPointers and recursion. Reverse a list, detect a cycle, BFS/DFS a tree, level-order traversal.
5 · Tries + Heaps + Backtracking14hPrefix trees, priority queues (the one Go gives you and JS doesn't), and exhaustive search with pruning.
6 · Graphs + Advanced Graphs16hIslands, topological sort / cycle detection, Dijkstra. The topic that separates offers from rejections.
7 · Dynamic Programming (1-D & 2-D)16hThe scary one, demystified: recognize the subproblem, memoize, then flatten to a table.
8 · Greedy, Intervals, Bit, Math10hThe long tail that still shows up: Jump Game, Merge Intervals, Single Number, Rotate Image.
9 · Mock interviews14hTimed, out-loud, whiteboard-style. This is where preparation becomes performance. Do ~20 mocks.
The single highest-leverage habit: after solving a problem, close it and re-solve it from a blank editor three days later. Passing once is recognition; passing cold is recall — and recall is what you need under interview pressure. The roadmap's "revisit problems cold after 3 days" tip is the whole game.

Pick a language: JavaScript or Go?

Both are excellent interview languages and this page teaches every solution in both. The honest trade-off:

  • JavaScript — ubiquitous, terse, and interviewers always know it. The catches: no built-in heap, sort() defaults to string order, and shift() on an array is O(n).
  • Go — fast, explicit, and its zero-value maps make counting elegant. It ships container/heap and sort.Slice. The catch: more ceremony (heap boilerplate, verbose error handling you can mostly skip in interviews).

Pick the one you'll be fastest and calmest in. If you know both, default to JavaScript for speed and reach for Go when the problem needs a heap or heavy integer math.

The interview stdlib, side by side

Ninety percent of problems need only these four tools. Learn them cold in your language so you never burn interview minutes on syntax.

Arrays & slices

// Arrays are dynamic; these are your workhorses.
const a = [3, 1, 2];
a.push(4);            // add to end      -> [3,1,2,4]
a.pop();              // remove from end -> 4
a.length;             // size

// SORTING GOTCHA: default sort is lexicographic (string order)!
[10, 2, 1].sort();              // -> [1, 10, 2]   ❌
[10, 2, 1].sort((x, y) => x - y); // -> [1, 2, 10]  ✅

// Iterating
for (const x of a) { /* value */ }
a.forEach((x, i) => {});
const doubled = a.map((x) => x * 2);

// Queue: shift() is O(n)! Use an index pointer for BFS instead:
let head = 0;
const q = [1, 2, 3];
while (head < q.length) { const x = q[head++]; }

🏋️ Practice first — try these before moving on

Warm-ups — write each from a blank editor:

  1. Reverse an array in place — no second array, just two pointers.
  2. Find the maximum value AND its index in a single pass.
  3. Remove duplicates from a sorted array in place, returning the new length.
  4. Rotate an array to the right by k positions.
  5. Move all zeros to the end while keeping the other elements' order.

Hash maps & sets

// Map preserves insertion order and takes any key type. Prefer it over {}.
const count = new Map();
count.set("a", (count.get("a") ?? 0) + 1);  // increment with default
count.has("a");     // true
count.get("a");     // 1
count.delete("a");
for (const [k, v] of count) {}              // iterate entries

// Set — membership in O(1)
const seen = new Set();
seen.add(5);
seen.has(5);        // true

// Frequency map idiom
const freq = new Map();
for (const c of "aab") freq.set(c, (freq.get(c) ?? 0) + 1);

🏋️ Practice first — try these before moving on

Warm-ups — write each from a blank editor:

  1. Count the frequency of each character in a string and return the map.
  2. Given two arrays, return their intersection (unique common elements).
  3. Find the first non-repeating character in a string.
  4. Detect whether an array contains any duplicate, in one pass.
  5. Given an array, return true if any two numbers sum to a target (Two Sum, from memory).

Heaps / priority queues

This is the biggest divergence between the two languages, so it's worth memorizing your side now — heaps power "top K", Dijkstra, and median problems later.

// JS has NO built-in heap. In an interview, either implement a tiny one
// or keep an array sorted. This min-heap is enough for most problems:
class MinHeap {
  heap = [];
  size() { return this.heap.length; }
  peek() { return this.heap[0]; }
  push(v) {
    this.heap.push(v);
    let i = this.heap.length - 1;
    while (i > 0) {
      const p = (i - 1) >> 1;
      if (this.heap[p] <= this.heap[i]) break;
      [this.heap[p], this.heap[i]] = [this.heap[i], this.heap[p]];
      i = p;
    }
  }
  pop() {
    const top = this.heap[0], last = this.heap.pop();
    if (this.heap.length) {
      this.heap[0] = last;
      let i = 0, n = this.heap.length;
      while (true) {
        let s = i, l = 2 * i + 1, r = 2 * i + 2;
        if (l < n && this.heap[l] < this.heap[s]) s = l;
        if (r < n && this.heap[r] < this.heap[s]) s = r;
        if (s === i) break;
        [this.heap[s], this.heap[i]] = [this.heap[i], this.heap[s]];
        i = s;
      }
    }
    return top;
  }
}

🏋️ Practice first — try these before moving on

Warm-ups — write each from a blank editor:

  1. Implement the min-heap above from a blank editor — push and pop, no peeking.
  2. Use your heap to print a stream of numbers' running minimum after each insert.
  3. Turn your min-heap into a max-heap by changing exactly one comparison.
  4. Given an array, use a size-k heap to keep only its k smallest elements.

Big-O in ninety seconds

Big-O is just: how does the work grow as the input grows? You only need to recognize a handful, and to say the right one out loud when asked.

ComplexityNameYou'll see it in
O(1)constanthash lookup, array index, math
O(log n)logarithmicbinary search, balanced-tree ops
O(n)linearone scan, two pointers, sliding window
O(n log n)linearithmicsorting, heap of n items
O(n²)quadraticnested loops, naive pair-checking
O(2ⁿ) / O(n!)exponential / factorialbacktracking, subsets, permutations
The interviewer's real question is always "can you do better than the brute force?" The move, over and over, is trading space for time: an O(n²) double loop becomes O(n) the moment you remember what you've seen in a hash map.

Here's that exact trade — the twelve most important lines on this page. Two Sum brute-forces as a double loop; a hash map of values you've already seen collapses it to a single pass:

function twoSum(nums, target) {
  const seen = new Map();          // value -> index
  for (let i = 0; i < nums.length; i++) {
    const need = target - nums[i];
    if (seen.has(need)) return [seen.get(need), i];
    seen.set(nums[i], i);
  }
  return [];
}
// twoSum([2,7,11,15], 9) -> [0, 1]

How to behave in the room

Solving isn't enough — FAANG hires for how you think out loud. The roadmap's interview tips, made concrete:

  • Clarify before you code. Restate the problem, ask about input size, empty inputs, duplicates, negatives. Two minutes here saves ten later and shows seniority.
  • State the brute force first, give its Big-O, then say "but we can do better" and improve. Interviewers want the journey, not a memorized answer teleported onto the board.
  • Pattern first, code second. Name the technique ("this is a sliding window") before typing. It anchors you and signals you've seen the shape.
  • Narrate as you type, and walk a tiny example through your code by hand to catch bugs before running.
  • Struggle a little before asking for help — but ask. Silence for ten minutes reads as stuck; "I'm weighing a heap versus sorting, here's the trade-off" reads as strong. Ask for hints, not solutions.
  • Always debrief. After solving, state the complexity, name one edge case you'd test, and one thing you'd improve with more time.
A note on this page's code. Every solution below was run through node and go run before publishing — the outputs shown are real. Type them out yourself, though; reading solutions builds recognition, and only writing them builds recall.
how to drill each problem
# 1. Read the problem. Restate it out loud.
# 2. Brute force + its Big-O (say it, don't skip it).
# 3. Improve to the target complexity. Name the pattern.
# 4. Code it in your primary language.
# 5. Walk a small example through by hand.
# 6. Re-code it in your second language for reinforcement.
# 7. THREE DAYS LATER: re-solve from a blank file. This is the rep that counts.
back to top
Pattern 01~8 hours

Arrays & Hashing

The foundation of half of all interview problems. The core insight: a hash map (or set) turns 'have I seen this before?' from an O(n) scan into an O(1) lookup — collapsing quadratic solutions to linear.

🎯 Recognize it when…

You're checking for duplicates, counting occurrences, grouping by some key, or asking "does the complement exist?" Any time your brute force is "for each element, scan the rest," a hash map is almost always the upgrade.

You already saw the archetype in Foundations — Two Sum. The pattern generalizes: build a map in one pass, then answer questions against it in O(1). Two more signature problems.

49. Group Anagrams

Mediumopen on LeetCode ↗
TimeO(n · k log k)SpaceO(n · k)

Group words that are anagrams of each other. The trick is finding a canonical key that's identical for all anagrams: the sorted letters. "eat", "tea" and "ate" all sort to "aet". Map that key to the list of words that produced it. (n = number of words, k = word length.)

function groupAnagrams(strs) {
  const groups = new Map();          // sortedKey -> list of words
  for (const s of strs) {
    // Two words are anagrams iff their sorted letters match.
    const key = [...s].sort().join("");
    if (!groups.has(key)) groups.set(key, []);
    groups.get(key).push(s);
  }
  return [...groups.values()];
}
// groupAnagrams(["eat","tea","tan","ate","nat","bat"])
//   -> [["eat","tea","ate"], ["tan","nat"], ["bat"]]
The "canonical key" idea is worth internalizing. Whenever you need to group "things that are secretly the same," ask: what value is identical across the group? Sorted letters here; a frequency signature "a2b1" if sorting is too slow.

347. Top K Frequent Elements

Mediumopen on LeetCode ↗
TimeO(n)SpaceO(n)

Return the k most frequent numbers. The obvious solution counts, then sorts by frequency — O(n log n). But we can do O(n) with bucket sort: a number can appear at most n times, so index an array by frequency and drop each number into its bucket. Walk buckets high-to-low and take the first k.

// Bucket sort by frequency: no sorting needed, O(n) overall.
function topKFrequent(nums, k) {
  const freq = new Map();
  for (const n of nums) freq.set(n, (freq.get(n) ?? 0) + 1);

  // buckets[f] = numbers that appear exactly f times. Max f is nums.length.
  const buckets = Array.from({ length: nums.length + 1 }, () => []);
  for (const [n, f] of freq) buckets[f].push(n);

  const out = [];
  for (let f = buckets.length - 1; f >= 0 && out.length < k; f--) {
    for (const n of buckets[f]) {
      out.push(n);
      if (out.length === k) break;
    }
  }
  return out;
}
// topKFrequent([1,1,1,2,2,3], 2) -> [1, 2]
Heap is the "expected" answer — but bucket sort beats it. Interviewers often expect a min-heap of size k (O(n log k)). Bucket sort is O(n) and shows deeper insight. Mention both: "a heap gives n log k, but since frequency is bounded by n, buckets give linear."

🏋️ Practice first — try these before moving on

You've met Two Sum, Group Anagrams and Top K. Now cement the pattern:

back to top
Pattern 02~5 hours

Two Pointers

Two indices walking a (usually sorted) array — from the ends inward, or both from the start. It replaces an O(n²) nested loop with a single O(n) sweep, using the sorted order to decide which pointer to move.

🎯 Recognize it when…

The array is sorted (or you can sort it), and you're looking for a pair or triple that satisfies a condition — a target sum, the two lines holding the most water, a palindrome check. Sorted order lets you move the left or right pointer with confidence instead of re-scanning.

125. Valid Palindrome

Easyopen on LeetCode ↗
TimeO(n)SpaceO(1)

Two pointers from the ends, walking inward. Skip non-alphanumeric characters, compare case-insensitively. The O(1) space matters: the naive answer builds a cleaned string and reverses it, but pointers need no extra memory.

function isPalindrome(s) {
  let l = 0, r = s.length - 1;
  const ok = (c) => /[a-z0-9]/i.test(c);   // alphanumeric only
  while (l < r) {
    while (l < r && !ok(s[l])) l++;        // skip junk from the left
    while (l < r && !ok(s[r])) r--;        // skip junk from the right
    if (s[l].toLowerCase() !== s[r].toLowerCase()) return false;
    l++; r--;
  }
  return true;
}
// isPalindrome("A man, a plan, a canal: Panama") -> true

15. 3Sum

Mediumopen on LeetCode ↗
TimeO(n²)SpaceO(1)

The pattern-defining problem. Find all unique triples summing to zero. Sort first, then fix one number and two-pointer the rest for the remaining pair — that's O(n) per anchor, O(n²) total, beating the O(n³) brute force. The fiddly part is skipping duplicates at both the anchor and the pointers so triples aren't repeated.

function threeSum(nums) {
  nums.sort((a, b) => a - b);      // sort enables the two-pointer sweep
  const res = [];
  for (let i = 0; i < nums.length - 2; i++) {
    if (nums[i] > 0) break;                    // all positive from here
    if (i > 0 && nums[i] === nums[i - 1]) continue; // skip dup anchor
    let l = i + 1, r = nums.length - 1;
    while (l < r) {
      const sum = nums[i] + nums[l] + nums[r];
      if (sum < 0) l++;
      else if (sum > 0) r--;
      else {
        res.push([nums[i], nums[l], nums[r]]);
        l++; r--;
        while (l < r && nums[l] === nums[l - 1]) l++;   // skip dup
        while (l < r && nums[r] === nums[r + 1]) r--;   // skip dup
      }
    }
  }
  return res;
}
// threeSum([-1,0,1,2,-1,-4]) -> [[-1,-1,2], [-1,0,1]]
Duplicate handling is where 3Sum interviews are won or lost. Three guards: skip a repeated anchor (nums[i] === nums[i-1]), and after recording a triple, advance l and r past their repeats. Miss any one and you emit duplicate triples.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 03~5 hours

Sliding Window

A window [left, right] over a contiguous run of an array or string. You expand the right edge to include, and contract the left edge when a constraint breaks — turning 'check every substring' (O(n²)) into one O(n) pass.

🎯 Recognize it when…

The problem asks about a contiguous subarray or substring — longest, shortest, or a max/min sum — subject to a constraint ("no repeats", "sum ≤ k", "at most 2 distinct"). If you catch yourself wanting to check every substring, slide a window instead.

121. Best Time to Buy and Sell Stock

Easyopen on LeetCode ↗
TimeO(n)SpaceO(1)

The gentlest window: track the cheapest price seen so far (the "buy" edge) and the best profit if you sold today. One pass, constant space. It's a window where the left edge only ever moves to a new minimum.

function maxProfit(prices) {
  let minSoFar = prices[0], best = 0;
  for (const p of prices) {
    minSoFar = Math.min(minSoFar, p);   // cheapest buy day seen
    best = Math.max(best, p - minSoFar); // best sell-today profit
  }
  return best;
}
// maxProfit([7,1,5,3,6,4]) -> 5   (buy at 1, sell at 6)

3. Longest Substring Without Repeating Characters

Mediumopen on LeetCode ↗
TimeO(n)SpaceO(min(n, charset))

The canonical variable-size window. Expand right; when the new character was already in the window, jump left to just past its previous position. A map of last-seen indices makes the jump O(1) instead of shrinking one step at a time.

function lengthOfLongestSubstring(s) {
  const lastSeen = new Map();   // char -> last index
  let left = 0, best = 0;
  for (let right = 0; right < s.length; right++) {
    const c = s[right];
    // If we've seen c inside the current window, jump left past it.
    if (lastSeen.has(c) && lastSeen.get(c) >= left) {
      left = lastSeen.get(c) + 1;
    }
    lastSeen.set(c, right);
    best = Math.max(best, right - left + 1);
  }
  return best;
}
// lengthOfLongestSubstring("abcabcbb") -> 3  ("abc")
Two window flavours to know. Fixed-size (window of exactly k): add the incoming element, remove the outgoing one. Variable-size (this problem): grow right greedily, shrink left only when the constraint breaks. Recognizing which one you're in tells you the loop shape immediately.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 04~5 hours

Stacks

Last-in-first-out. Perfect for matching pairs, and for the 'monotonic stack' — keeping a stack in sorted order to answer 'next greater/smaller element' problems in a single O(n) pass.

🎯 Recognize it when…

You need to match or nest things (brackets, tags), undo the most recent action, or find the next greater/smaller element for each item. That last one is the monotonic stack — a genuine FAANG favourite.

20. Valid Parentheses

Easyopen on LeetCode ↗
TimeO(n)SpaceO(n)

Push openers; on a closer, the top of the stack must be its matching opener. Valid iff every closer matched and the stack ends empty. The textbook use of a stack for nesting.

function isValid(s) {
  const stack = [];
  const pairs = { ")": "(", "]": "[", "}": "{" };
  for (const c of s) {
    if (c in pairs) {
      // Closing bracket must match the most recent opener.
      if (stack.pop() !== pairs[c]) return false;
    } else {
      stack.push(c);   // opener
    }
  }
  return stack.length === 0;   // nothing left unclosed
}
// isValid("([]{})") -> true ;  isValid("(]") -> false

739. Daily Temperatures

Mediumopen on LeetCode ↗
TimeO(n)SpaceO(n)

For each day, how many days until a warmer one? The monotonic stack: keep a stack of day-indices whose temperatures are decreasing. When today is warmer than the top, we've just resolved that day's wait — pop and record the distance. Each index is pushed and popped once, so it's O(n) despite the nested loop.

function dailyTemperatures(temps) {
  const res = new Array(temps.length).fill(0);
  const stack = [];   // indices of days awaiting a warmer day (decreasing temps)
  for (let i = 0; i < temps.length; i++) {
    // While today is warmer than the day on top, we've found its answer.
    while (stack.length && temps[i] > temps[stack[stack.length - 1]]) {
      const j = stack.pop();
      res[j] = i - j;
    }
    stack.push(i);
  }
  return res;
}
// dailyTemperatures([73,74,75,71,69,72,76,73]) -> [1,1,4,2,1,1,0,0]
The monotonic-stack tell: "for each element, find the next one that is bigger/smaller." Same skeleton solves Next Greater Element, Largest Rectangle in Histogram, and Trapping Rain Water. Learn it once, reuse it five times.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 06~6 hours

Linked Lists

Nodes chained by pointers. The whole topic is really one skill — manipulating three pointers (prev, curr, next) without losing the rest of the list — plus the fast/slow trick that solves cycles, midpoints, and nth-from-end in one pass.

🎯 Recognize it when…

You're given a linked list and asked to reverse it, detect or find a cycle, find the middle or the nth-from-end, or merge sorted lists. Two techniques cover almost all of it: the pointer-flip and the fast/slow (two-speed) pointers.
Draw the pointers. On a whiteboard, sketch three boxes and the arrows, then move them one line at a time. Linked-list bugs are almost always "I overwrote next before I saved it." The next = curr.next save on line one exists precisely to prevent that.

206. Reverse Linked List

Easyopen on LeetCode ↗
TimeO(n)SpaceO(1)

The most important linked-list problem — its three-pointer dance shows up as a subroutine inside dozens of harder ones (reverse in k-groups, reorder list, palindrome list). Memorize the four lines until they're muscle memory.

// ListNode = { val, next }
function reverseList(head) {
  let prev = null, curr = head;
  while (curr) {
    const next = curr.next;   // save the rest of the list
    curr.next = prev;         // flip the pointer
    prev = curr;              // advance prev
    curr = next;              // advance curr
  }
  return prev;                // new head
}
// 1->2->3->null  becomes  3->2->1->null

141. Linked List Cycle

Easyopen on LeetCode ↗
TimeO(n)SpaceO(1)

Floyd's tortoise and hare. A slow pointer takes one step, a fast pointer takes two. If there's a cycle, the fast one eventually laps the slow one and they collide; if not, fast runs off the end. O(1) space — no hash set of visited nodes needed.

// Floyd's tortoise & hare: slow moves 1 step, fast moves 2.
// If there's a loop, fast eventually laps slow and they meet.
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;   // fast fell off the end -> no cycle
}
Fast/slow does more than cycles. Start fast at the head and it finds the middle when it reaches the end. Give fast an n-node head start and it finds the nth-from-end. Same two pointers, three classic problems.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 07~8 hours

Trees

Recursion's home turf. Almost every tree problem is 'do something at this node, then recurse left and right, then combine.' Master that shape plus level-order BFS and you've covered the vast majority of tree questions.

🎯 Recognize it when…

Anything phrased over a binary tree — depth, inversion, path sums, lowest common ancestor, "is it balanced/symmetric?". Reach for DFS recursion by default; switch to BFS with a queue when the problem cares about levels or the shortest path.
The universal tree-recursion template: handle the null base case, recurse on left and right, then combine their results with the current node. Depth combines with max + 1; "is balanced" combines by checking the height gap; path sum combines by adding. Same skeleton, different combine step.

104. Maximum Depth of Binary Tree

Easyopen on LeetCode ↗
TimeO(n)SpaceO(h)

The template in its purest form — internalize this shape and most "Easy" tree problems fall out of it. Space is O(h), the recursion-stack height: O(log n) for a balanced tree, O(n) worst case for a degenerate one.

// TreeNode = { val, left, right }
function maxDepth(root) {
  if (!root) return 0;                    // base case: empty subtree
  return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
// The whole shape of tree recursion: base case, recurse both sides, combine.

102. Binary Tree Level Order Traversal

Mediumopen on LeetCode ↗
TimeO(n)SpaceO(n)

When the answer is grouped by level, recursion is awkward — use BFS with a queue. The key move: capture the queue's size at the start of each level (or swap in a fresh next array, as here) so you process exactly one level before moving on.

function levelOrder(root) {
  const res = [];
  if (!root) return res;
  let queue = [root];                 // BFS, one level at a time
  while (queue.length) {
    const level = [], next = [];
    for (const node of queue) {
      level.push(node.val);
      if (node.left) next.push(node.left);
      if (node.right) next.push(node.right);
    }
    res.push(level);
    queue = next;                     // whole next level at once
  }
  return res;
}
// A tree [3,9,20,null,null,15,7] -> [[3], [9,20], [15,7]]
"Level", "shortest", or "minimum steps" ⇒ BFS. This queue-per-level skeleton is identical to the grid BFS you'll use in Number of Islands and Word Ladder. Trees are just graphs that happen not to have cycles.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 08~4 hours

Tries (Prefix Trees)

A tree where each edge is a character and each root-to-node path spells a prefix. It makes 'is this a known word?' and 'does any word start with this?' both O(word length), independent of how many words you've stored.

🎯 Recognize it when…

The problem is about prefixes or a dictionary of words — autocomplete, spell-check, "does any word start with…", or word search on a board. If you're about to check a set of strings against a growing prefix repeatedly, that's a trie.

208. Implement Trie (Prefix Tree)

Mediumopen on LeetCode ↗
TimeO(L) per opSpaceO(total chars)

Build the data structure itself: insert, search (full word), and startsWith (prefix). Each node holds a map (or fixed 26-slot array) of child characters and an isEnd flag. The only difference between search and startsWith is whether you require that end flag. L = word length.

class TrieNode {
  children = {};       // char -> TrieNode
  isEnd = false;       // does a word end here?
}

class Trie {
  root = new TrieNode();

  insert(word) {
    let node = this.root;
    for (const c of word) {
      node.children[c] ??= new TrieNode();   // create branch if missing
      node = node.children[c];
    }
    node.isEnd = true;
  }

  search(word) {
    const node = this._find(word);
    return node !== null && node.isEnd;      // must be a full word
  }

  startsWith(prefix) {
    return this._find(prefix) !== null;      // any node is enough
  }

  _find(str) {
    let node = this.root;
    for (const c of str) {
      if (!node.children[c]) return null;
      node = node.children[c];
    }
    return node;
  }
}
Go tip: a fixed [26]*Trie array beats a map for lowercase-only inputs — no hashing, better cache behaviour, and the interviewer notices. Use a map only when the alphabet is large or unknown. Once you have this, Design Add and Search Words (with . wildcards) is the same trie plus a DFS that branches on the dot.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 09~5 hours

Heap / Priority Queue

A heap always hands you the smallest (or largest) element in O(log n), without keeping everything sorted. It's the right tool for 'top K', 'kth largest', 'merge k lists', and streaming medians — and it's the one structure Go gives you but JavaScript doesn't.

🎯 Recognize it when…

You need the k largest/smallest, the kth something, to merge k sorted streams, or to track a running median. Signal phrase: "top K" or "kth" with a big input — a heap gives O(n log k) where full sorting gives O(n log n).
Language reality check. Go has container/heap (the boilerplate is in Foundations). JavaScript has nothing built in — interviewers accept a short hand-rolled heap, or a clever workaround like the bucket sort from Top-K-Frequent. Know your heap code cold before the interview if JS is your language.

215. Kth Largest Element in an Array

Mediumopen on LeetCode ↗
TimeO(n log k)SpaceO(k)

The counter-intuitive trick: to find the kth largest, keep a min-heap of size k. Push everything; whenever it exceeds k, evict the smallest. What survives is the k largest elements, and the smallest of those — the heap's root — is your answer.

// Keep a MIN-heap of the k largest seen so far. Its root is the answer.
// (Reusing the MinHeap from Foundations.)
function findKthLargest(nums, k) {
  const heap = new MinHeap();
  for (const n of nums) {
    heap.push(n);
    if (heap.size() > k) heap.pop();   // evict the smallest -> keep top k
  }
  return heap.peek();                  // kth largest = smallest of the top k
}
// findKthLargest([3,2,1,5,6,4], 2) -> 5

295. Find Median from Data Stream

Hardopen on LeetCode ↗
TimeO(log n) addSpaceO(n)

The elegant two-heap classic. Split the numbers into a lower half (a max-heap, so its largest is at the root) and an upper half (a min-heap). Keep the sizes within one of each other, and the median is either the larger heap's root or the average of the two roots — both O(1) to read, O(log n) to insert.

// Two heaps: a MAX-heap for the lower half, a MIN-heap for the upper half.
// Keep them balanced; the median is a root (or the average of both roots).
class MedianFinder {
  low = new MaxHeap();    // lower half (max at root)
  high = new MinHeap();   // upper half (min at root)

  addNum(num) {
    this.low.push(num);
    this.high.push(this.low.pop());        // funnel largest-of-low into high
    if (this.high.size() > this.low.size())
      this.low.push(this.high.pop());      // rebalance: low keeps the extra
  }

  findMedian() {
    if (this.low.size() > this.high.size()) return this.low.peek();
    return (this.low.peek() + this.high.peek()) / 2;
  }
}
Two heaps facing each other is a pattern in its own right — medians, "IPO", and sliding-window median all use a low max-heap and a high min-heap kept in balance. (MaxHeap is the Foundations heap with Less flipped to >.)

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 10~6 hours

Backtracking

Systematic exhaustive search: choose, explore, un-choose. You build a candidate step by step, recurse, then undo the last choice to try the next — walking the whole decision tree while pruning branches that can't lead anywhere.

🎯 Recognize it when…

The problem asks for all of something — all subsets, all permutations, all combinations, all valid board placements — or "does any arrangement satisfy…". The answer count is exponential, so you're generating candidates, not computing a formula.
The template never changes: choose → explore → un-choose. Push a choice onto path, recurse, then pop it. The only per-problem decisions are (1) what counts as a complete solution (the base case) and (2) how to prune dead branches early. Get those two right and the skeleton writes itself.

78. Subsets

Mediumopen on LeetCode ↗
TimeO(n · 2ⁿ)SpaceO(n)

The cleanest introduction: here every node of the decision tree is a valid subset, so you record path at the top of every call. Passing i + 1 as the next start prevents reusing an element and prevents [2,1] after [1,2] — no duplicate sets.

function subsets(nums) {
  const res = [], path = [];
  const backtrack = (start) => {
    res.push([...path]);              // every node is a valid subset
    for (let i = start; i < nums.length; i++) {
      path.push(nums[i]);             // choose
      backtrack(i + 1);              // explore (i+1: no reuse, no dup order)
      path.pop();                    // un-choose
    }
  };
  backtrack(0);
  return res;
}
// subsets([1,2,3]) -> [[],[1],[1,2],[1,2,3],[1,3],[2],[2,3],[3]]
Copy the path when you record it. path is mutated throughout the search — pushing the same reference (JS) or slice header (Go) means every stored "subset" ends up empty or aliased. [...path] / copy() is not optional.

39. Combination Sum

Mediumopen on LeetCode ↗
TimeO(n^(T/min))SpaceO(T/min)

Same skeleton, two tweaks. The base case is remaining === 0 (we hit the target), and we prune when remaining < 0 (overshot). The single most important line: recurse with i, not i + 1, because a number may be reused — that's the whole difference from Subsets.

function combinationSum(candidates, target) {
  const res = [], path = [];
  const backtrack = (start, remaining) => {
    if (remaining === 0) { res.push([...path]); return; }
    if (remaining < 0) return;                  // overshot: prune
    for (let i = start; i < candidates.length; i++) {
      path.push(candidates[i]);
      backtrack(i, remaining - candidates[i]);  // i, not i+1: reuse allowed
      path.pop();
    }
  };
  backtrack(0, target);
  return res;
}
// combinationSum([2,3,6,7], 7) -> [[2,2,3], [7]]
The start index is the dial that tunes backtracking. i + 1 = each element once (Subsets, Combinations). i = reuse allowed (Combination Sum). Start from 0 every time with a "used" set = order matters (Permutations). Same template, three different dials.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 11~8 hours

Graphs

Nodes and edges — and the topic that most often decides a FAANG loop. Almost everything reduces to two traversals: BFS (a queue, shortest path in unweighted graphs) and DFS (recursion, connectivity and cycles). Grids are just graphs where neighbours are the four adjacent cells.

🎯 Recognize it when…

Anything with connections: a grid of land/water, courses with prerequisites, nodes and edges, "can you reach…", "how many groups…", "is there a cycle". DFS for connectivity/components/cycles; BFS when you need the shortest path in an unweighted graph.

200. Number of Islands

Mediumopen on LeetCode ↗
TimeO(rows · cols)SpaceO(rows · cols)

The archetypal grid problem. Scan every cell; each time you hit unseen land, that's a new island — increment the count and flood-fill (DFS) every connected land cell to "0" so you never count it again. Marking visited in place saves a separate visited set.

function numIslands(grid) {
  const rows = grid.length, cols = grid[0].length;
  let count = 0;

  const sink = (r, c) => {                     // DFS flood-fill
    if (r < 0 || c < 0 || r >= rows || c >= cols || grid[r][c] !== "1") return;
    grid[r][c] = "0";                          // mark visited (sink the land)
    sink(r + 1, c); sink(r - 1, c);
    sink(r, c + 1); sink(r, c - 1);
  };

  for (let r = 0; r < rows; r++)
    for (let c = 0; c < cols; c++)
      if (grid[r][c] === "1") { count++; sink(r, c); }  // new island

  return count;
}
The four-direction flood-fill is a reusable engine. Max Area of Island, Rotting Oranges (BFS version), Surrounded Regions, and Pacific Atlantic Water Flow are all this same grid traversal with a different bookkeeping tweak.

207. Course Schedule

Mediumopen on LeetCode ↗
TimeO(V + E)SpaceO(V + E)

"Can you finish all courses given prerequisites?" is really "does this directed graph have a cycle?" Kahn's topological sort: track each node's in-degree (unmet prereqs), repeatedly take any node with in-degree 0, and decrement its neighbours. If you can take all of them, there's no cycle.

function canFinish(numCourses, prerequisites) {
  const graph = Array.from({ length: numCourses }, () => []);
  const indeg = new Array(numCourses).fill(0);
  for (const [course, pre] of prerequisites) {
    graph[pre].push(course);   // pre -> course
    indeg[course]++;
  }

  // Kahn's algorithm: repeatedly take a course with no remaining prereqs.
  const queue = [];
  for (let i = 0; i < numCourses; i++) if (indeg[i] === 0) queue.push(i);

  let taken = 0, head = 0;
  while (head < queue.length) {
    const c = queue[head++];
    taken++;
    for (const next of graph[c]) {
      if (--indeg[next] === 0) queue.push(next);
    }
  }
  return taken === numCourses;   // all taken => no cycle
}
// canFinish(2, [[1,0]]) -> true ;  canFinish(2, [[1,0],[0,1]]) -> false
Topological sort = "order things with dependencies." Course Schedule II (return the order), Alien Dictionary, and build-system ordering are the same in-degree BFS. If instead you need cycle detection in an undirected graph, reach for union-find.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 12~8 hours

Advanced Graphs

Weighted graphs, where edges have costs. The one algorithm to know cold is Dijkstra — shortest path from a source when edge weights are non-negative — implemented with a min-heap that always expands the closest unsettled node next.

🎯 Recognize it when…

Edges carry weights (time, distance, cost) and you need the cheapest path or minimum total. Non-negative weights ⇒ Dijkstra. Also in this family: minimum spanning tree (Prim/Kruskal, as in Min Cost to Connect All Points) and shortest path with at most K stops (Bellman-Ford).

743. Network Delay Time

Mediumopen on LeetCode ↗
TimeO(E log V)SpaceO(V + E)

Classic single-source shortest path. A signal starts at node k; how long until it reaches everyone? Dijkstra: a min-heap keyed by distance always pops the closest unsettled node, relaxes its edges, and pushes improved distances. The answer is the farthest of all final distances (or −1 if anyone is unreachable).

// Dijkstra with a min-heap of [dist, node]. Returns the time for the
// signal to reach ALL nodes, or -1 if some are unreachable.
function networkDelayTime(times, n, k) {
  const graph = Array.from({ length: n + 1 }, () => []);
  for (const [u, v, w] of times) graph[u].push([v, w]);

  const dist = new Array(n + 1).fill(Infinity);
  dist[k] = 0;
  const heap = [[0, k]];                 // [distance, node], min by distance

  while (heap.length) {
    heap.sort((a, b) => a[0] - b[0]);    // simplest correct PQ (small inputs)
    const [d, u] = heap.shift();
    if (d > dist[u]) continue;           // stale entry
    for (const [v, w] of graph[u]) {
      if (d + w < dist[v]) {
        dist[v] = d + w;
        heap.push([dist[v], v]);
      }
    }
  }

  let ans = 0;
  for (let i = 1; i <= n; i++) ans = Math.max(ans, dist[i]);
  return ans === Infinity ? -1 : ans;
}
// networkDelayTime([[2,1,1],[2,3,1],[3,4,1]], 4, 2) -> 2
The "stale entry" guard is essential. A node can sit in the heap multiple times with different distances. When you pop one whose distance is worse than the best already recorded (d > dist[u]), skip it. Without that check you re-process nodes and can produce wrong answers. (The JS version re-sorts the array as a stand-in for a real priority queue — fine for interview-sized inputs; use a proper heap for large graphs.)

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 13~8 hours

1-D Dynamic Programming

The topic people fear needlessly. DP is just recursion where you cache repeated subproblems. The recipe: find the recurrence ('the answer here depends on the answer one/two steps back'), then either memoize the recursion or fill a 1-D table bottom-up.

🎯 Recognize it when…

The answer for input n is built from answers to smaller inputs, and those smaller answers get asked for repeatedly. Telltale prompts: "in how many ways…", "the minimum/maximum to reach…", "can you make…". If a greedy choice can be wrong, it's usually DP.
Four questions crack any DP. (1) What's the state (what does dp[i] mean)? (2) What's the recurrence (how does dp[i] relate to earlier entries)? (3) What are the base cases? (4) What order fills the table? Answer those four in words before writing code and the code becomes transcription.

70. Climbing Stairs

Easyopen on LeetCode ↗
TimeO(n)SpaceO(1)

The "hello world" of DP. Ways to reach step i = ways to i-1 (take one step) + ways to i-2 (take two) — it's Fibonacci. Since each state needs only the previous two, two variables replace the whole table: O(1) space.

function climbStairs(n) {
  // Ways to reach step i = ways to i-1 (one step) + ways to i-2 (two steps).
  // That's Fibonacci. Keep only the last two values -> O(1) space.
  let oneBack = 1, twoBack = 1;
  for (let i = 2; i <= n; i++) {
    [twoBack, oneBack] = [oneBack, oneBack + twoBack];
  }
  return oneBack;
}
// climbStairs(5) -> 8

198. House Robber

Mediumopen on LeetCode ↗
TimeO(n)SpaceO(1)

Can't rob two adjacent houses. At each house the choice is binary: skip it (carry the best-so-far) or rob it (best from two houses back, plus this one). dp[i] = max(dp[i-1], dp[i-2] + nums[i]). A greedy "always take the biggest" fails here — which is exactly why it's DP.

function rob(nums) {
  // At each house: skip it (keep prev best) or rob it (best before last + this).
  let prev = 0, prev2 = 0;
  for (const n of nums) {
    const take = prev2 + n;
    [prev2, prev] = [prev, Math.max(prev, take)];
  }
  return prev;
}
// rob([2,7,9,3,1]) -> 12   (rob houses 2 + 9 + 1)
The "take it or leave it" recurrence is everywhere. House Robber II (circular), Coin Change (min coins to a target), and Longest Increasing Subsequence are all "for each item, the best answer is max/min over including or excluding it."

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 14~8 hours

2-D Dynamic Programming

Same idea as 1-D DP, but the state needs two indices — usually 'the answer for the first i of one thing and first j of another.' A grid, or two strings compared position by position. Fill a 2-D table; often you can then roll it down to one row.

🎯 Recognize it when…

Two things vary independently: a grid's row and column, or two strings/sequences compared index-by-index (edit distance, common subsequence, matching). If dp[i] isn't enough state and you need dp[i][j], you're here.

62. Unique Paths

Mediumopen on LeetCode ↗
TimeO(m · n)SpaceO(n)

A robot moves only right or down across an m×n grid — how many paths to the corner? Paths to a cell = paths from the cell above + paths from the left. That's a 2-D table, but since each row depends only on the row above, a single rolling row collapses it to O(n) space.

function uniquePaths(m, n) {
  // Paths to a cell = paths from above + paths from the left.
  // One row is enough: row[j] += row[j-1] rolls the 2-D table into 1-D.
  const row = new Array(n).fill(1);
  for (let i = 1; i < m; i++)
    for (let j = 1; j < n; j++)
      row[j] += row[j - 1];
  return row[n - 1];
}
// uniquePaths(3, 7) -> 28

1143. Longest Common Subsequence

Mediumopen on LeetCode ↗
TimeO(m · n)SpaceO(m · n)

The template for all two-string DP. dp[i][j] = LCS length of the suffixes starting at a[i] and b[j]. If the characters match, add one and move both diagonally; otherwise take the best of advancing one string or the other. The padding row/column of zeros is the base case that makes the recurrence uniform.

function longestCommonSubsequence(a, b) {
  const m = a.length, n = b.length;
  // dp[i][j] = LCS length of a[i:] and b[j:]. Extra row/col of zeros = base.
  const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
  for (let i = m - 1; i >= 0; i--) {
    for (let j = n - 1; j >= 0; j--) {
      if (a[i] === b[j]) dp[i][j] = 1 + dp[i + 1][j + 1];       // match
      else dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);     // skip one
    }
  }
  return dp[0][0];
}
// longestCommonSubsequence("abcde", "ace") -> 3   ("ace")
Match / skip-one / skip-the-other is the shape of Edit Distance, Distinct Subsequences, and Regular Expression Matching too. Learn to see the two-string grid and half the "Hard" DP problems become fill-in-the-recurrence exercises.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 15~4 hours

Greedy

Make the locally optimal choice at each step and never look back. It's O(n) and beautiful when it works — but it only works when a local optimum is provably global. The skill is spotting which problems allow it (and which are DP in disguise).

🎯 Recognize it when…

You can make an obvious best-right-now choice — the farthest reachable index, the largest coin, the earliest-finishing interval — and never need to reconsider it. If you can construct a case where the greedy choice backfires, it's DP instead. When unsure, greedy is worth trying first because it's so much simpler.

53. Maximum Subarray (Kadane's Algorithm)

Mediumopen on LeetCode ↗
TimeO(n)SpaceO(1)

The greedy classic. Walk the array keeping a running sum; at each step make one greedy decision — extend the current subarray or restart from the current element (whichever is larger). The moment your running sum would drag the next element down, drop it. Track the best sum seen.

function maxSubArray(nums) {
  // At each element, either extend the running sum or restart from here.
  let best = nums[0], curr = nums[0];
  for (let i = 1; i < nums.length; i++) {
    curr = Math.max(nums[i], curr + nums[i]);   // restart vs extend
    best = Math.max(best, curr);
  }
  return best;
}
// maxSubArray([-2,1,-3,4,-1,2,1,-5,4]) -> 6   (subarray [4,-1,2,1])

55. Jump Game

Mediumopen on LeetCode ↗
TimeO(n)SpaceO(1)

Can you reach the last index? The greedy insight: track the farthest index reachable so far. Sweep left to right; if you ever stand on an index beyond that frontier, you're stuck. Otherwise extend the frontier. No DP table needed — one number.

function canJump(nums) {
  let reach = 0;                       // farthest index reachable so far
  for (let i = 0; i < nums.length; i++) {
    if (i > reach) return false;       // stuck before this index
    reach = Math.max(reach, i + nums[i]);
  }
  return true;
}
// canJump([2,3,1,1,4]) -> true ;  canJump([3,2,1,0,4]) -> false
Greedy's honest caveat: it's only correct if you can argue the local choice is safe. In interviews, say the argument out loud ("extending the reach never hurts, because…"). An unjustified greedy that happens to pass examples is a red flag; a justified one is a green light.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 16~4 hours

Intervals

Problems about ranges — [start, end] pairs — that overlap, merge, or need scheduling. The universal first move is to sort by start time (occasionally by end time). After sorting, a single linear sweep resolves almost everything.

🎯 Recognize it when…

The input is a list of [start, end] pairs: merge overlaps, insert a new one, count non-overlapping, find the minimum meeting rooms. The reflex: sort by start, then sweep once comparing each interval to the previous.

56. Merge Intervals

Mediumopen on LeetCode ↗
TimeO(n log n)SpaceO(n)

Sort by start. Then sweep: if the current interval starts at or before the last kept interval's end, they overlap — extend the end; otherwise there's a gap, so start a new interval. The sort is the O(n log n) cost; the sweep is linear.

function merge(intervals) {
  intervals.sort((a, b) => a[0] - b[0]);   // sort by start — the whole trick
  const res = [intervals[0]];
  for (let i = 1; i < intervals.length; i++) {
    const [start, end] = intervals[i];
    const last = res[res.length - 1];
    if (start <= last[1]) last[1] = Math.max(last[1], end);  // overlap: extend
    else res.push([start, end]);                             // gap: new interval
  }
  return res;
}
// merge([[1,3],[2,6],[8,10],[15,18]]) -> [[1,6],[8,10],[15,18]]

57. Insert Interval

Mediumopen on LeetCode ↗
TimeO(n)SpaceO(n)

Here the list is already sorted, so you skip the sort and get O(n). Three phases: copy all intervals that end before the new one starts, merge everything that overlaps into one widened interval, then copy the rest. Clean, and a favourite because the phases must be exactly right.

function insert(intervals, newInterval) {
  const res = [];
  let [start, end] = newInterval, i = 0;
  const n = intervals.length;

  while (i < n && intervals[i][1] < start) res.push(intervals[i++]); // before
  while (i < n && intervals[i][0] <= end) {                          // overlap
    start = Math.min(start, intervals[i][0]);
    end = Math.max(end, intervals[i][1]);
    i++;
  }
  res.push([start, end]);
  while (i < n) res.push(intervals[i++]);                            // after
  return res;
}
// insert([[1,3],[6,9]], [2,5]) -> [[1,5],[6,9]]
Sort by START to merge; sort by END to schedule. Non-overlapping Intervals and Meeting Rooms II hinge on sorting by end time (greedily keep the interval that frees up soonest). Knowing which key to sort on is the entire insight for interval problems.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 17~3 hours

Bit Manipulation

Operating on the binary representation directly. A small toolkit — XOR to cancel pairs, AND/shift to inspect bits, n & (n-1) to drop the lowest set bit — solves a recurring set of problems in O(1) space that look impossible otherwise.

🎯 Recognize it when…

The problem mentions bits explicitly, or asks for a constant-space trick on integers where the obvious solution needs a hash set — counting set bits, finding the one unpaired number, swapping without a temp. The tells: XOR, powers of two, "without extra memory".
The bit toolkit worth memorizing: x ^ x === 0 (pairs cancel), x & 1 (is it odd / last bit), x >> 1 (divide by two), x & (x - 1) (clears the lowest set bit — loop it to count bits), and 1 << k (the k-th bit as a mask). This is the same bit-manipulation from the numbering-systems interlude in my C++ course, applied to interview problems.

136. Single Number

Easyopen on LeetCode ↗
TimeO(n)SpaceO(1)

Every number appears twice except one — find it in O(1) space. XOR the whole array: identical values cancel to zero (a ^ a = 0), and zero XOR the lonely number leaves it standing. A hash set works too, but XOR needs no memory and delights interviewers.

function singleNumber(nums) {
  // XOR: a^a = 0 and a^0 = a. Pairs cancel; the lonely number survives.
  let x = 0;
  for (const n of nums) x ^= n;
  return x;
}
// singleNumber([4,1,2,1,2]) -> 4

338. Counting Bits

Easyopen on LeetCode ↗
TimeO(n)SpaceO(n)

Count set bits for every number from 0 to n. The naive way counts each number independently; the elegant way is DP on bits: i has all the bits of i >> 1 (i with its last bit shifted off) plus its own last bit i & 1. One pass, each entry O(1).

function countBits(n) {
  // dp[i] = dp[i >> 1] + (i & 1): i has the bits of i/2, plus its last bit.
  const dp = new Array(n + 1).fill(0);
  for (let i = 1; i <= n; i++) dp[i] = dp[i >> 1] + (i & 1);
  return dp;
}
// countBits(5) -> [0,1,1,2,1,2]

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
Pattern 18~3 hours

Math & Geometry

Grid mechanics and number tricks. No deep theory — the challenge is careful index bookkeeping: rotating a matrix in place, spiralling through it with four moving boundaries, or handling overflow. Precision under pressure, not cleverness.

🎯 Recognize it when…

You're transforming or traversing a matrix in a specific geometric way (rotate, spiral, diagonal), or doing integer math with an overflow or precision twist (Pow, Happy Number, palindrome number). The difficulty is bookkeeping, so slow down and track indices deliberately.

48. Rotate Image

Mediumopen on LeetCode ↗
TimeO(n²)SpaceO(1)

Rotate an n×n matrix 90° clockwise in place. The elegant decomposition: transpose (swap across the diagonal), then reverse each row. Two simple passes beat trying to cycle four cells at a time — and it's far easier to get right under pressure.

function rotate(matrix) {
  const n = matrix.length;
  // Rotate 90° clockwise = transpose, then reverse each row. In place.
  for (let i = 0; i < n; i++)
    for (let j = i + 1; j < n; j++)
      [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]]; // transpose
  for (const row of matrix) row.reverse();                        // mirror
  return matrix;
}
// rotate([[1,2,3],[4,5,6],[7,8,9]]) -> [[7,4,1],[8,5,2],[9,6,3]]

54. Spiral Matrix

Mediumopen on LeetCode ↗
TimeO(m · n)SpaceO(1)

Read the matrix in spiral order. Keep four boundaries — top, bottom, left, right — and walk one edge at a time (right, down, left, up), shrinking the matching boundary after each edge. The two if guards prevent re-reading a row or column when the remaining region is a single line.

function spiralOrder(matrix) {
  const res = [];
  let top = 0, bottom = matrix.length - 1;
  let left = 0, right = matrix[0].length - 1;
  while (top <= bottom && left <= right) {
    for (let c = left; c <= right; c++) res.push(matrix[top][c]);      // →
    top++;
    for (let r = top; r <= bottom; r++) res.push(matrix[r][right]);    // ↓
    right--;
    if (top <= bottom)
      for (let c = right; c >= left; c--) res.push(matrix[bottom][c]); // ←
    bottom--;
    if (left <= right)
      for (let r = bottom; r >= top; r--) res.push(matrix[r][left]);   // ↑
    left++;
  }
  return res;
}
// spiralOrder([[1,2,3],[4,5,6],[7,8,9]]) -> [1,2,3,6,9,8,7,4,5]
You've reached the end of the roadmap. Eighteen patterns, each a lens that turns a scary blank problem into "oh, this is a sliding window." From here it's reps: drill the "More Exercises" on each roadmap node, do timed mocks out loud, and re-solve cold after three days. Pattern recognition + calm execution is the whole game — go get the offer.

🏋️ Practice first — try these before moving on

Templates learned — now build recall. Solve each from a blank editor, then re-solve cold in three days:

back to top
That's the whole map. Patterns give you recognition; reps give you recall; mocks give you calm. Put in the 100 hours, re-solve cold, and walk into that loop ready. Good luck — you've got this.