Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Depth-First Search (DFS)

Builds on Bottom-Up DFS and Fast Power, which you haven’t finished yet.

P3HardMental model~5 min speedrun

Binary Lifting (Kth Ancestor / LCA)

Kth Ancestor of a Tree Node (LC 1483)(opens leetcode.com)

A tree has n nodes named 0 to n - 1, and node 0 is its root. You get it as a list parent: parent[i] is the node directly above node i, and parent[0] = -1 because nothing is above the root.

Going up from a node one edge at a time, the first node you reach is its 1st ancestor (its parent), the next one its 2nd ancestor, and so on. Write a class TreeAncestor:

  • TreeAncestor(n, parent) receives the tree once.
  • getKthAncestor(node, k) returns the k-th ancestor of node, or -1 when fewer than k nodes lie above node.

The same tree answers up to 5 * 104 questions.

Example 1
Input:["TreeAncestor", "getKthAncestor", "getKthAncestor", "getKthAncestor"] [[7, [-1, 0, 0, 1, 1, 2, 2]], [3, 1], [5, 2], [6, 3]]
Output:[null, 1, 0, -1]
Node 0 is the root, nodes 1 and 2 hang under it, 3 and 4 under 1, and 5 and 6 under 2. One step up from 3 is 1. Two steps up from 5 are 2, then 0. From 6, the path up is 2, then 0, and then nothing: 6 has only two ancestors, so the 3rd one does not exist.
3140526

Binary Lifting

Spot it when: One tree, or any map where each item has exactly one "next" item, and many questions of the form "where do I land after k steps up" (the k-th ancestor, the lowest common ancestor of two nodes, a path between them), with k and the number of questions too large to walk one step at a time.
Not this one if…
  • Only one pair of nodes is asked about, once, as in Lowest Common Ancestor of a Binary Tree (LC 236): a table built to answer many questions never pays for itself. See Bottom-Up DFS
Binary Lifting
Python
1class TreeAncestor:
2 """Kth Ancestor of a Tree Node: up[j][v] is v's 2^j-th ancestor, so any k is a few
3 jumps.
4 Time: O(N log N) to build, O(log N) per query | Space: O(N log N)
5 """
6 
7 def __init__(self, n: int, parent: list[int]) -> None:
8 self.LOG = max(1, n.bit_length()) # k <= n, so bits 0..LOG-1 cover every k
9 self.up = [parent[:]] # up[0][v]: v's parent (-1 for the root)
10 for j in range(1, self.LOG):
11 prev = self.up[j - 1]
12 # Two jumps of 2^(j-1) make one jump of 2^j; past the root stays -1
13 self.up.append([-1 if prev[v] == -1 else prev[prev[v]] for v in range(n)])
14 
15 def getKthAncestor(self, node: int, k: int) -> int:
16 for j in range(self.LOG):
17 if k >> j & 1: # Bit j of k is set: jump 2^j levels up
18 node = self.up[j][node]
19 if node == -1:
20 return -1 # Past the root: stop before up[j][-1] wraps around
21 return node
Invariant: self.up[j][v] is the node exactly 2^j steps above v, or -1 when fewer than 2^j nodes lie above it.

Store, for every node, where 2^j steps up lands: self.up[0] is the parent list, and each next level is two jumps of the level before, prev[prev[v]]. Then any k is a sum of powers of two, so getKthAncestor jumps once for each set bit of k: node = self.up[j][node] whenever k >> j & 1. This is correct because the jumps add up: 2^j steps followed by 2^j more is exactly 2^(j+1), and the bits of k add up to k, in any order. self.LOG = max(1, n.bit_length()) levels are enough, because k <= n. The trap is the root. A node with fewer than 2^j nodes above it has -1 in level j, and -1 must stay -1: check prev[v] == -1 before reading prev[prev[v]], and return -1 as soon as node == -1. In Python, prev[-1] is not an error but the last node's entry, so an unguarded table quietly turns "no ancestor" into a real node.

See it run

Binary lifting: build up[j], then jump along the bits of k

levels built 1 · k 5 = 101 in binary

levels built 2 · up[1][9] 6

levels built 3 · up[2][9] 1

levels built 4 · up[3][9] -1

node 9 · bit j - · k 101

node 8 · bit j 0 · k 101

node 8 · bit j 1 · k 101

node 0 · bit j 2 · k 101

node 0 · bit j 3 · k 101

answer 0 · node 0

Each circle is a tree node. The rows under the tree are up[0], up[1], ...: entry v of up[j] is the ancestor 2^j steps above node v, or -1.

  • now the node the table or the query is working on
  • halfway the node reached after the first of the two jumps
  • lands the node a finished jump lands on

The tree has 10 nodes, so LOG = 4 levels are enough for any k up to 10. up[0] is the parent list.

Level 1: from node 9, 2^0 steps reach 8. A second 2^0 steps reach 6, so up[1][9] is 6.

Level 2: from node 9, 2^1 steps reach 6. A second 2^1 steps reach 1, so up[2][9] is 1.

Level 3: from node 9, 2^2 steps reach 1. A second 2^2 steps pass the root, so up[3][9] is -1.

Query: getKthAncestor(9, 5). In binary, k is 101, so the jumps are the set bits of k.

Bit 0 of k is set. Jump 2^0 steps: node = up[0][9] = 8.

Bit 1 of k is 0, so there is no jump at level 1.

Bit 2 of k is set. Jump 2^2 steps: node = up[2][8] = 0.

Bit 3 of k is 0, so there is no jump at level 3.

All bits are used. getKthAncestor(9, 5) returns 0.

9now867341520
up[0]
-1
0
0
1
1
2
3
3
6
8
up[1]
empty
up[2]
empty
up[3]
empty
1 / 10
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Reading prev[prev[v]] without checking prev[v] == -1

    Mistake
    self.up.append([prev[prev[v]] for v in range(n)])
    Fix
    self.up.append([-1 if prev[v] == -1 else prev[prev[v]] for v in range(n)])

    What happens: -1 means that the node has no ancestor that far up. Python reads prev[-1] as the last entry of the list, not as an error. So an unchecked prev[prev[v]] turns a missing ancestor into a real node. The table then holds wrong jumps.

    Example: Kth Ancestor of a Tree Node (LC 1483): n = 7, parent = [-1,0,0,1,1,2,2], getKthAncestor(1, 3)
    Mistake: returns 2Fix: returns -1
  2. 2

    Gotcha 2: Writing int(math.log2(n)) levels instead of n.bit_length()

    Mistake
    self.LOG = int(math.log2(n))
    Fix
    self.LOG = max(1, n.bit_length())

    What happens: k can be as large as n. On a chain of 8 nodes, k = 8 needs the jump of 23, which is level 3. int(math.log2(8)) is 3, so the levels are only 0 to 2. The bit for 8 is never tested. The level count comes from the largest k. In Maximize Value of Function in a Ball Passing Game (LC 2836), k reaches 1010, so use k.bit_length() levels.

    Example: n = 8, parent = [-1,0,1,2,3,4,5,6] (a chain), getKthAncestor(7, 8)
    Mistake: returns 7Fix: returns -1
  3. 3

    Gotcha 3: Filling one node's levels at a time

    What happens: Level j of node v reads level j - 1 of another node, prev[v]. When one node's levels are filled at a time, that other node's row may not be built yet. LeetCode does not promise parent[i] < i, so a parent can come later in the list. Build self.up[j] from the whole of self.up[j - 1], one level at a time.

  4. 4

    Gotcha 4: Testing k & j instead of k >> j & 1

    Mistake
    if k & j:
    Fix
    if k >> j & 1:

    What happens: k >> j & 1 reads bit j of k. k & j combines k with the level number j itself, bit by bit. For k = 2, k & 1 is 0, so the jump of 2 is skipped. k & 2 is 2 at j = 2, so the code jumps 4 levels instead.

    Example: n = 7, parent = [-1,0,0,1,1,2,2], getKthAncestor(5, 2)
    Mistake: returns -1Fix: returns 0
  5. 5

    Gotcha 5: Jumping again after node becomes -1

    Mistake
    node = self.up[j][node]
    Fix
    node = self.up[j][node]
    if node == -1:
        return -1

    What happens: Once node is -1, the walk has passed the root. A later jump reads self.up[j][-1]. Python reads that as the last node's entry, so the walk lands on a real node. Return -1 as soon as node == -1.

    Example: n = 8, parent = [-1,0,1,2,3,4,5,6] (a chain), getKthAncestor(1, 6)
    Mistake: returns 3Fix: returns -1

Deep Dive

Why the bits of k are enough

self.up[j][v] is the node 2^j steps above v. Level 0 is the parent list. Level j is two jumps of level j - 1: up[j][v] = up[j - 1][up[j - 1][v]]. Two jumps of 2^(j-1) steps make one jump of 2^j steps, so the rule is always true.

Every number k is a sum of different powers of two, and its binary digits say which ones. For k = 5, the binary form is 101, so 5 = 4 + 1. Climbing 1 step and then 4 steps is the same as climbing 5 steps. The order of the jumps does not matter, because every jump goes up the same single chain of parents.

So getKthAncestor visits each level j from 0 up, and jumps only when bit j of k is set. The loop runs LOG times, where LOG = max(1, n.bit_length()). One query costs O(log N), and the walk-up-one-step loop would cost O(k).

Example: in Kth Ancestor of a Tree Node (LC 1483), take parent = [-1, 0, 0, 1, 1, 2, 3, 3, 6, 8] (10 nodes). Node 9 has the ancestors 8, 6, 3, 1, 0, in that order. getKthAncestor(9, 5) jumps 1 step to node 8, then 4 steps to node 0. The answer is 0.

One trace: fill the table, then answer a query

The board uses the tree from the previous section. The rows under the tree are up[0], up[1], up[2] and up[3]. LOG is 4, because n = 10 has bit_length 4.

The build steps follow node 9. up[1][9] is 6: one step reaches 8, a second step reaches 6. up[2][9] is 1: two steps reach 6, two more steps reach 1. up[3][9] is -1: four steps reach 1, but node 1 has only one ancestor, so four more steps pass the root. That step is marked as the trap, and the check prev[v] == -1 is the reason the answer stays -1.

The query is getKthAncestor(9, 5), and 5 is 101 in binary. Bit 0 is set, so the node becomes up[0][9] = 8. Bit 1 is 0, so there is no jump. Bit 2 is set, so the node becomes up[2][8] = 0. Bit 3 is 0. The answer is 0, which matches the five parent steps 9, 8, 6, 3, 1, 0.

The trap: -1 must stay -1

-1 means "no ancestor that far up". A negative index in Python does not raise an error. It reads the list from the end, so prev[-1] is the last entry of prev.

Suppose the table is built as prev[prev[v]] with no check. For the tree above, up[0][0] is -1. Then up[1][0] reads prev[-1], which is prev[9], which is 8. The root now claims that two steps up is node 8. The table holds wrong jumps, and no exception is raised.

The same table gives getKthAncestor(9, 7) = 8. Node 9 has only 5 ancestors, so the correct answer is -1. The guard has two parts. In the build, write -1 if prev[v] == -1 else prev[prev[v]]. In the query, return -1 as soon as node == -1.

The level count can also be wrong. int(math.log2(n)) gives 3 for n = 8, but k = 8 needs bit 3. Use n.bit_length(). When k is larger than n, as in Maximize Value of Function in a Ball Passing Game (LC 2836), use k.bit_length().

The cost, and the neighbouring card

The table has LOG rows and n entries in each row. Building it costs O(N log N) time and O(N log N) space. After that, each query costs O(log N). For n = 50000 and 50000 queries, that is about 16 steps for each query, and no query walks the whole chain.

The table pays off only when there are many queries. A single question about two nodes, as in Lowest Common Ancestor of a Binary Tree (LC 236), is answered by one walk with the Ask Children card, and the table is not worth building.

The same table also finds the lowest common ancestor of many pairs. First lift the deeper node up by the difference of the depths, with the bits of that difference. Then lift both nodes together, from the highest level down to level 0, and jump only when the two landing nodes differ. Minimum Edge Weight Equilibrium Queries in a Tree (LC 2846) uses this idea.

The build must go level by level. up[j] needs the whole of up[j - 1], and a parent can come later in the list than its child, because the input does not promise parent[i] < i.

60-Second Pitch

"One question about two nodes needs only a walk. Here there are many queries about one tree, so I build a table first. up[j][v] is the node 2^j steps above v, or -1. Level 0 is the parent list. Each next level is two jumps of the level before: up[j][v] = up[j - 1][up[j - 1][v]], with -1 staying -1. To answer getKthAncestor(node, k), I read the bits of k. For each set bit j, I jump to up[j][node], and I return -1 as soon as the node is -1. The jumps add up to k steps. The build costs O(N log N) time and space, and each query costs O(log N). The level count is n.bit_length(), because k is at most n."

Common Questions

Why is the table correct? Why are two jumps of 2^(j-1) one jump of 2^j?

Every jump goes from a node to its parent chain, so steps simply add. The node 2^(j-1) steps above v is up[j - 1][v]. Going 2^(j-1) steps above that node is up[j - 1][up[j - 1][v]]. The total is 2^j steps above v.

What are the time and space of the build and of one query?

The build is O(N log N) time and O(N log N) space, because there are LOG rows of n entries. One query is O(log N), one step for each bit of k. A plain walk up costs O(k) per query, which is too slow for many large k.

How many levels do you need?

Enough levels for the largest k you will be asked. In Kth Ancestor of a Tree Node (LC 1483), k <= n, so max(1, n.bit_length()) levels cover every bit. If k can be larger than n, you can return -1 early, or use k.bit_length() levels as in Maximize Value of Function in a Ball Passing Game (LC 2836).

How do you find the lowest common ancestor with this table?

Compute the depth of each node. Lift the deeper node by the difference of the depths, using the bits of the difference. If the two nodes are now equal, that node is the answer. Otherwise go from the highest level down to level 0, and jump both nodes together whenever their landing nodes differ. The parent of the final pair is the lowest common ancestor.

When would you not build this table?

When there is one question, or only a few. A single walk, as in Lowest Common Ancestor of a Binary Tree (LC 236), costs O(N) with no preparation. The table costs O(N log N) to build, so it pays off only when the number of queries is large.

SWE Reasoning

Spot it

  • Many queries ask "where do I land after k steps" on one tree, or on a map where every item has exactly one next item.
  • k or the number of queries is too large to walk one step at a time.
  • The wording says k-th ancestor, lowest common ancestor of two nodes, or a path between two nodes.
  • The structure does not change between queries, so a table built once can be reused.
  • k can be as large as 1010, so a loop over the steps is not possible.

The invariant, step by step

  1. up[0] is the parent list: up[0][v] is the node 1 step above v, or -1 for the root.
  2. For j >= 1, up[j][v] is two jumps of up[j - 1]. Two jumps of 2^(j-1) steps are 2^j steps, so the invariant holds one level at a time.
  3. If prev[v] is -1, the node has no ancestor 2^(j-1) steps up, so it has none 2^j steps up. The entry stays -1.
  4. A query reads the bits of k. For each set bit j, it moves node to up[j][node]. The jumps add up to exactly k steps, in any order.
  5. If node becomes -1, the walk has passed the root, and the query returns -1 at once. Otherwise node is the k-th ancestor after the last bit.

Traps

  • prev[prev[v]] with no check for -1. Python reads prev[-1] as the last entry, so a missing ancestor becomes a real node.
  • Jumping again after node is -1. The next jump reads up[j][-1], which is the last node's entry. Return -1 first.
  • int(math.log2(n)) levels. For n = 8, k = 8 needs bit 3, and the table has only levels 0 to 2. Use n.bit_length().
  • k & j in place of k >> j & 1. The first form mixes k with the level number. The second reads bit j.
  • Filling one node's levels at a time. Level j needs another node's level j - 1, and that row may not be built yet. Fill one whole level at a time.

Practice

Canonical LeetCode:Kth Ancestor of a Tree Node (LC 1483)Minimum Edge Weight Equilibrium Queries in a Tree (LC 2846)Maximize Value of Function in a Ball Passing Game (LC 2836)Number of Ways to Assign Edge Weights II (LC 3559)
Theory Context•Depth-First Search (DFS)

Builds on Bottom-Up DFS and Fast Power, which you haven’t finished yet.

P3HardMental model~5 min speedrun

Binary Lifting (Kth Ancestor / LCA)

Kth Ancestor of a Tree Node (LC 1483)(opens leetcode.com)

A tree has n nodes named 0 to n - 1, and node 0 is its root. You get it as a list parent: parent[i] is the node directly above node i, and parent[0] = -1 because nothing is above the root.

Going up from a node one edge at a time, the first node you reach is its 1st ancestor (its parent), the next one its 2nd ancestor, and so on. Write a class TreeAncestor:

  • TreeAncestor(n, parent) receives the tree once.
  • getKthAncestor(node, k) returns the k-th ancestor of node, or -1 when fewer than k nodes lie above node.

The same tree answers up to 5 * 104 questions.

Example 1
Input:["TreeAncestor", "getKthAncestor", "getKthAncestor", "getKthAncestor"] [[7, [-1, 0, 0, 1, 1, 2, 2]], [3, 1], [5, 2], [6, 3]]
Output:[null, 1, 0, -1]
Node 0 is the root, nodes 1 and 2 hang under it, 3 and 4 under 1, and 5 and 6 under 2. One step up from 3 is 1. Two steps up from 5 are 2, then 0. From 6, the path up is 2, then 0, and then nothing: 6 has only two ancestors, so the 3rd one does not exist.
3140526

Binary Lifting

Spot it when: One tree, or any map where each item has exactly one "next" item, and many questions of the form "where do I land after k steps up" (the k-th ancestor, the lowest common ancestor of two nodes, a path between them), with k and the number of questions too large to walk one step at a time.
Not this one if…
  • Only one pair of nodes is asked about, once, as in Lowest Common Ancestor of a Binary Tree (LC 236): a table built to answer many questions never pays for itself. See Bottom-Up DFS
Binary Lifting
Python
1class TreeAncestor:
2 """Kth Ancestor of a Tree Node: up[j][v] is v's 2^j-th ancestor, so any k is a few
3 jumps.
4 Time: O(N log N) to build, O(log N) per query | Space: O(N log N)
5 """
6 
7 def __init__(self, n: int, parent: list[int]) -> None:
8 self.LOG = max(1, n.bit_length()) # k <= n, so bits 0..LOG-1 cover every k
9 self.up = [parent[:]] # up[0][v]: v's parent (-1 for the root)
10 for j in range(1, self.LOG):
11 prev = self.up[j - 1]
12 # Two jumps of 2^(j-1) make one jump of 2^j; past the root stays -1
13 self.up.append([-1 if prev[v] == -1 else prev[prev[v]] for v in range(n)])
14 
15 def getKthAncestor(self, node: int, k: int) -> int:
16 for j in range(self.LOG):
17 if k >> j & 1: # Bit j of k is set: jump 2^j levels up
18 node = self.up[j][node]
19 if node == -1:
20 return -1 # Past the root: stop before up[j][-1] wraps around
21 return node
Invariant: self.up[j][v] is the node exactly 2^j steps above v, or -1 when fewer than 2^j nodes lie above it.

Store, for every node, where 2^j steps up lands: self.up[0] is the parent list, and each next level is two jumps of the level before, prev[prev[v]]. Then any k is a sum of powers of two, so getKthAncestor jumps once for each set bit of k: node = self.up[j][node] whenever k >> j & 1. This is correct because the jumps add up: 2^j steps followed by 2^j more is exactly 2^(j+1), and the bits of k add up to k, in any order. self.LOG = max(1, n.bit_length()) levels are enough, because k <= n. The trap is the root. A node with fewer than 2^j nodes above it has -1 in level j, and -1 must stay -1: check prev[v] == -1 before reading prev[prev[v]], and return -1 as soon as node == -1. In Python, prev[-1] is not an error but the last node's entry, so an unguarded table quietly turns "no ancestor" into a real node.

See it run

Binary lifting: build up[j], then jump along the bits of k

levels built 1 · k 5 = 101 in binary

levels built 2 · up[1][9] 6

levels built 3 · up[2][9] 1

levels built 4 · up[3][9] -1

node 9 · bit j - · k 101

node 8 · bit j 0 · k 101

node 8 · bit j 1 · k 101

node 0 · bit j 2 · k 101

node 0 · bit j 3 · k 101

answer 0 · node 0

Each circle is a tree node. The rows under the tree are up[0], up[1], ...: entry v of up[j] is the ancestor 2^j steps above node v, or -1.

  • now the node the table or the query is working on
  • halfway the node reached after the first of the two jumps
  • lands the node a finished jump lands on

The tree has 10 nodes, so LOG = 4 levels are enough for any k up to 10. up[0] is the parent list.

Level 1: from node 9, 2^0 steps reach 8. A second 2^0 steps reach 6, so up[1][9] is 6.

Level 2: from node 9, 2^1 steps reach 6. A second 2^1 steps reach 1, so up[2][9] is 1.

Level 3: from node 9, 2^2 steps reach 1. A second 2^2 steps pass the root, so up[3][9] is -1.

Query: getKthAncestor(9, 5). In binary, k is 101, so the jumps are the set bits of k.

Bit 0 of k is set. Jump 2^0 steps: node = up[0][9] = 8.

Bit 1 of k is 0, so there is no jump at level 1.

Bit 2 of k is set. Jump 2^2 steps: node = up[2][8] = 0.

Bit 3 of k is 0, so there is no jump at level 3.

All bits are used. getKthAncestor(9, 5) returns 0.

9now867341520
up[0]
-1
0
0
1
1
2
3
3
6
8
up[1]
empty
up[2]
empty
up[3]
empty
1 / 10
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Reading prev[prev[v]] without checking prev[v] == -1

    Mistake
    self.up.append([prev[prev[v]] for v in range(n)])
    Fix
    self.up.append([-1 if prev[v] == -1 else prev[prev[v]] for v in range(n)])

    What happens: -1 means that the node has no ancestor that far up. Python reads prev[-1] as the last entry of the list, not as an error. So an unchecked prev[prev[v]] turns a missing ancestor into a real node. The table then holds wrong jumps.

    Example: Kth Ancestor of a Tree Node (LC 1483): n = 7, parent = [-1,0,0,1,1,2,2], getKthAncestor(1, 3)
    Mistake: returns 2Fix: returns -1
  2. 2

    Gotcha 2: Writing int(math.log2(n)) levels instead of n.bit_length()

    Mistake
    self.LOG = int(math.log2(n))
    Fix
    self.LOG = max(1, n.bit_length())

    What happens: k can be as large as n. On a chain of 8 nodes, k = 8 needs the jump of 23, which is level 3. int(math.log2(8)) is 3, so the levels are only 0 to 2. The bit for 8 is never tested. The level count comes from the largest k. In Maximize Value of Function in a Ball Passing Game (LC 2836), k reaches 1010, so use k.bit_length() levels.

    Example: n = 8, parent = [-1,0,1,2,3,4,5,6] (a chain), getKthAncestor(7, 8)
    Mistake: returns 7Fix: returns -1
  3. 3

    Gotcha 3: Filling one node's levels at a time

    What happens: Level j of node v reads level j - 1 of another node, prev[v]. When one node's levels are filled at a time, that other node's row may not be built yet. LeetCode does not promise parent[i] < i, so a parent can come later in the list. Build self.up[j] from the whole of self.up[j - 1], one level at a time.

  4. 4

    Gotcha 4: Testing k & j instead of k >> j & 1

    Mistake
    if k & j:
    Fix
    if k >> j & 1:

    What happens: k >> j & 1 reads bit j of k. k & j combines k with the level number j itself, bit by bit. For k = 2, k & 1 is 0, so the jump of 2 is skipped. k & 2 is 2 at j = 2, so the code jumps 4 levels instead.

    Example: n = 7, parent = [-1,0,0,1,1,2,2], getKthAncestor(5, 2)
    Mistake: returns -1Fix: returns 0
  5. 5

    Gotcha 5: Jumping again after node becomes -1

    Mistake
    node = self.up[j][node]
    Fix
    node = self.up[j][node]
    if node == -1:
        return -1

    What happens: Once node is -1, the walk has passed the root. A later jump reads self.up[j][-1]. Python reads that as the last node's entry, so the walk lands on a real node. Return -1 as soon as node == -1.

    Example: n = 8, parent = [-1,0,1,2,3,4,5,6] (a chain), getKthAncestor(1, 6)
    Mistake: returns 3Fix: returns -1

Deep Dive

Why the bits of k are enough

self.up[j][v] is the node 2^j steps above v. Level 0 is the parent list. Level j is two jumps of level j - 1: up[j][v] = up[j - 1][up[j - 1][v]]. Two jumps of 2^(j-1) steps make one jump of 2^j steps, so the rule is always true.

Every number k is a sum of different powers of two, and its binary digits say which ones. For k = 5, the binary form is 101, so 5 = 4 + 1. Climbing 1 step and then 4 steps is the same as climbing 5 steps. The order of the jumps does not matter, because every jump goes up the same single chain of parents.

So getKthAncestor visits each level j from 0 up, and jumps only when bit j of k is set. The loop runs LOG times, where LOG = max(1, n.bit_length()). One query costs O(log N), and the walk-up-one-step loop would cost O(k).

Example: in Kth Ancestor of a Tree Node (LC 1483), take parent = [-1, 0, 0, 1, 1, 2, 3, 3, 6, 8] (10 nodes). Node 9 has the ancestors 8, 6, 3, 1, 0, in that order. getKthAncestor(9, 5) jumps 1 step to node 8, then 4 steps to node 0. The answer is 0.

One trace: fill the table, then answer a query

The board uses the tree from the previous section. The rows under the tree are up[0], up[1], up[2] and up[3]. LOG is 4, because n = 10 has bit_length 4.

The build steps follow node 9. up[1][9] is 6: one step reaches 8, a second step reaches 6. up[2][9] is 1: two steps reach 6, two more steps reach 1. up[3][9] is -1: four steps reach 1, but node 1 has only one ancestor, so four more steps pass the root. That step is marked as the trap, and the check prev[v] == -1 is the reason the answer stays -1.

The query is getKthAncestor(9, 5), and 5 is 101 in binary. Bit 0 is set, so the node becomes up[0][9] = 8. Bit 1 is 0, so there is no jump. Bit 2 is set, so the node becomes up[2][8] = 0. Bit 3 is 0. The answer is 0, which matches the five parent steps 9, 8, 6, 3, 1, 0.

The trap: -1 must stay -1

-1 means "no ancestor that far up". A negative index in Python does not raise an error. It reads the list from the end, so prev[-1] is the last entry of prev.

Suppose the table is built as prev[prev[v]] with no check. For the tree above, up[0][0] is -1. Then up[1][0] reads prev[-1], which is prev[9], which is 8. The root now claims that two steps up is node 8. The table holds wrong jumps, and no exception is raised.

The same table gives getKthAncestor(9, 7) = 8. Node 9 has only 5 ancestors, so the correct answer is -1. The guard has two parts. In the build, write -1 if prev[v] == -1 else prev[prev[v]]. In the query, return -1 as soon as node == -1.

The level count can also be wrong. int(math.log2(n)) gives 3 for n = 8, but k = 8 needs bit 3. Use n.bit_length(). When k is larger than n, as in Maximize Value of Function in a Ball Passing Game (LC 2836), use k.bit_length().

The cost, and the neighbouring card

The table has LOG rows and n entries in each row. Building it costs O(N log N) time and O(N log N) space. After that, each query costs O(log N). For n = 50000 and 50000 queries, that is about 16 steps for each query, and no query walks the whole chain.

The table pays off only when there are many queries. A single question about two nodes, as in Lowest Common Ancestor of a Binary Tree (LC 236), is answered by one walk with the Ask Children card, and the table is not worth building.

The same table also finds the lowest common ancestor of many pairs. First lift the deeper node up by the difference of the depths, with the bits of that difference. Then lift both nodes together, from the highest level down to level 0, and jump only when the two landing nodes differ. Minimum Edge Weight Equilibrium Queries in a Tree (LC 2846) uses this idea.

The build must go level by level. up[j] needs the whole of up[j - 1], and a parent can come later in the list than its child, because the input does not promise parent[i] < i.

60-Second Pitch

"One question about two nodes needs only a walk. Here there are many queries about one tree, so I build a table first. up[j][v] is the node 2^j steps above v, or -1. Level 0 is the parent list. Each next level is two jumps of the level before: up[j][v] = up[j - 1][up[j - 1][v]], with -1 staying -1. To answer getKthAncestor(node, k), I read the bits of k. For each set bit j, I jump to up[j][node], and I return -1 as soon as the node is -1. The jumps add up to k steps. The build costs O(N log N) time and space, and each query costs O(log N). The level count is n.bit_length(), because k is at most n."

Common Questions

Why is the table correct? Why are two jumps of 2^(j-1) one jump of 2^j?

Every jump goes from a node to its parent chain, so steps simply add. The node 2^(j-1) steps above v is up[j - 1][v]. Going 2^(j-1) steps above that node is up[j - 1][up[j - 1][v]]. The total is 2^j steps above v.

What are the time and space of the build and of one query?

The build is O(N log N) time and O(N log N) space, because there are LOG rows of n entries. One query is O(log N), one step for each bit of k. A plain walk up costs O(k) per query, which is too slow for many large k.

How many levels do you need?

Enough levels for the largest k you will be asked. In Kth Ancestor of a Tree Node (LC 1483), k <= n, so max(1, n.bit_length()) levels cover every bit. If k can be larger than n, you can return -1 early, or use k.bit_length() levels as in Maximize Value of Function in a Ball Passing Game (LC 2836).

How do you find the lowest common ancestor with this table?

Compute the depth of each node. Lift the deeper node by the difference of the depths, using the bits of the difference. If the two nodes are now equal, that node is the answer. Otherwise go from the highest level down to level 0, and jump both nodes together whenever their landing nodes differ. The parent of the final pair is the lowest common ancestor.

When would you not build this table?

When there is one question, or only a few. A single walk, as in Lowest Common Ancestor of a Binary Tree (LC 236), costs O(N) with no preparation. The table costs O(N log N) to build, so it pays off only when the number of queries is large.

SWE Reasoning

Spot it

  • Many queries ask "where do I land after k steps" on one tree, or on a map where every item has exactly one next item.
  • k or the number of queries is too large to walk one step at a time.
  • The wording says k-th ancestor, lowest common ancestor of two nodes, or a path between two nodes.
  • The structure does not change between queries, so a table built once can be reused.
  • k can be as large as 1010, so a loop over the steps is not possible.

The invariant, step by step

  1. up[0] is the parent list: up[0][v] is the node 1 step above v, or -1 for the root.
  2. For j >= 1, up[j][v] is two jumps of up[j - 1]. Two jumps of 2^(j-1) steps are 2^j steps, so the invariant holds one level at a time.
  3. If prev[v] is -1, the node has no ancestor 2^(j-1) steps up, so it has none 2^j steps up. The entry stays -1.
  4. A query reads the bits of k. For each set bit j, it moves node to up[j][node]. The jumps add up to exactly k steps, in any order.
  5. If node becomes -1, the walk has passed the root, and the query returns -1 at once. Otherwise node is the k-th ancestor after the last bit.

Traps

  • prev[prev[v]] with no check for -1. Python reads prev[-1] as the last entry, so a missing ancestor becomes a real node.
  • Jumping again after node is -1. The next jump reads up[j][-1], which is the last node's entry. Return -1 first.
  • int(math.log2(n)) levels. For n = 8, k = 8 needs bit 3, and the table has only levels 0 to 2. Use n.bit_length().
  • k & j in place of k >> j & 1. The first form mixes k with the level number. The second reads bit j.
  • Filling one node's levels at a time. Level j needs another node's level j - 1, and that row may not be built yet. Fill one whole level at a time.

Practice

Canonical LeetCode:Kth Ancestor of a Tree Node (LC 1483)Minimum Edge Weight Equilibrium Queries in a Tree (LC 2846)Maximize Value of Function in a Ball Passing Game (LC 2836)Number of Ways to Assign Edge Weights II (LC 3559)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•