Kth Ancestor of a Tree Node
HardPrompt
You are given a tree with n nodes numbered from 0 to n - 1 in the form of a parent array parent where parent[i] is the parent of ith node. The root of the tree is node 0. Find the kth ancestor of a given node.
The kth ancestor of a tree node is the kth node in the path from that node to the root node.
Implement the TreeAncestor class:
TreeAncestor(int n, int[] parent)Initializes the object with the number of nodes in the tree and the parent array.int getKthAncestor(int node, int k)return thekthancestor of the given nodenode. If there is no such ancestor, return-1.
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]
Explanation
TreeAncestor treeAncestor = new TreeAncestor(7, [-1, 0, 0, 1, 1, 2, 2]);
treeAncestor.getKthAncestor(3, 1); // returns 1 which is the parent of 3
treeAncestor.getKthAncestor(5, 2); // returns 0 which is the grandparent of 5
treeAncestor.getKthAncestor(6, 3); // returns -1 because there is no such ancestor
Constraints:
1 <= k <= n <= 5 * 104parent.length == nparent[0] == -10 <= parent[i] < nfor all0 < i < n0 <= node < n- There will be at most
5 * 104queries.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves directly simulating the process of finding an ancestor. To find the k-th ancestor of a node, we simply traverse upwards from the given node towards the root, one step at a time, for k steps.
Algorithm
-
- In the constructor, store the
parentarray.
- In the constructor, store the
-
- In
getKthAncestor(node, k), initializecurrentNodetonode.
- In
-
- Loop
ktimes:
- Loop
-
- If
currentNodeis-1, break the loop and return-1.
- If
-
- Update
currentNodetoparent[currentNode].
- Update
-
- After the loop, return
currentNode.
- After the loop, return
Walkthrough
Constructor TreeAncestor(n, parent)
The constructor's role is minimal. It just needs to store the parent array for later use. This can be done by copying the input array into a member variable.
Method getKthAncestor(node, k)
This method implements the upward traversal.
- It starts with the given
node. - It then enters a loop that runs
ktimes. - In each iteration, it updates the current node to its parent using the stored
parentarray (node = parent[node]). - It also checks if the current node becomes
-1. If it does, it means we've reached past the root before completingksteps, so the k-th ancestor does not exist. In this case, we return-1immediately. - If the loop completes without the node becoming
-1, the final value ofnodeis the k-th ancestor.
class TreeAncestor { int[] parent; public TreeAncestor(int n, int[] parent) { this.parent = parent; } public int getKthAncestor(int node, int k) { int currentNode = node; for (int i = 0; i < k; i++) { if (currentNode == -1) { return -1; } currentNode = this.parent[currentNode]; } return currentNode; }}Complexity
Time
Constructor: `O(n)` to copy the parent array. `getKthAncestor`: `O(k)` for each query, which is `O(n)` in the worst case. Total for `Q` queries: `O(n + Q * n)`. This is too slow given the constraints and will result in Time Limit Exceeded.
Space
`O(n)` to store the parent array.
Trade-offs
Pros
Very simple to understand and implement.
Low memory usage.
Fast constructor.
Cons
Inefficient for queries with large
k.Fails to pass time limits on platforms like LeetCode for the given constraints.
This is a highly efficient technique that balances precomputation time with query time. The core idea is to precompute the 2^i-th ancestor for every node u and for every i such that 2^i < n. Any jump of size k can then be decomposed into a series of jumps of sizes that are powers of two. This allows us to find the k-th ancestor in logarithmic time.
Algorithm
Constructor
-
- Determine
LOG, the maximum power needed (e.g.,ceil(log2(n))).
- Determine
-
- Create a 2D array
up[n][LOG].
- Create a 2D array
-
- For each node
i, setup[i][0] = parent[i].
- For each node
-
- For
jfrom 1 toLOG-1:
- For
-
- For
ifrom 0 ton-1:
- For
-
-
Calculate `p = up[i][j-1]`.
-
-
-
If `p` is not -1, set `up[i][j] = up[p][j-1]`, otherwise set it to -1.
-
getKthAncestor
-
- Initialize
currentNodetonode.
- Initialize
-
- For
jfromLOG-1down to 0:
- For
-
- If the
j-th bit ofkis 1 (i.e.,(k >> j) & 1 == 1):
- If the
-
-
Update `currentNode = up[currentNode][j]`.
-
-
-
If `currentNode` becomes -1, return -1 immediately.
-
-
- Return
currentNode.
- Return
Walkthrough
Precomputation in TreeAncestor(n, parent)
We use a 2D array, up[n][LOG], where LOG is a value slightly larger than log2(n). up[u][j] stores the 2^j-th ancestor of node u. The maximum height of the tree is n, so we need 2^LOG > n. A safe choice for LOG is ceil(log2(n)). For n=50000, log2(50000) ≈ 15.6, so LOG=16 is sufficient.
- Base Case: The
2^0 = 1-st ancestor of any nodeuis its direct parent. So, we initialize the first column of our table:up[u][0] = parent[u]for allu. - DP Calculation: We can compute the rest of the table using the following recurrence: The
2^j-th ancestor ofuis the2^(j-1)-th ancestor of the2^(j-1)-th ancestor ofu. The formula isup[u][j] = up[ up[u][j-1] ][j-1]. We iteratejfrom 1 toLOG-1, and for eachj, we iterateufrom 0 ton-1.
Querying in getKthAncestor(node, k)
To find the k-th ancestor, we express k in binary. For example, k=13 is 1101 in binary, which is 8 + 4 + 1. So, to jump 13 steps, we can jump 8 steps, then 4 steps, then 1 step. We iterate from the most significant bit of k downwards (i.e., j from LOG-1 to 0). If the j-th bit of k is set, it means we need to make a jump of size 2^j. We update our current node: node = up[node][j]. If at any point node becomes -1, we know the ancestor doesn't exist, and we can return -1.
class TreeAncestor { private int[][] up; private int LOG; public TreeAncestor(int n, int[] parent) { LOG = (int) (Math.log(n) / Math.log(2)) + 1; up = new int[n][LOG]; for (int i = 0; i < n; i++) { up[i][0] = parent[i]; } for (int j = 1; j < LOG; j++) { for (int i = 0; i < n; i++) { if (up[i][j - 1] == -1) { up[i][j] = -1; } else { up[i][j] = up[up[i][j - 1]][j - 1]; } } } } public int getKthAncestor(int node, int k) { if (node == -1) { return -1; } for (int j = LOG - 1; j >= 0; j--) { if ((k >> j & 1) == 1) { node = up[node][j]; if (node == -1) { return -1; } } } return node; }}Complexity
Time
Constructor: `O(n * log n)` for precomputation to fill the `up` table. `getKthAncestor`: `O(log k)` or `O(log n)` for each query. Total for `Q` queries: `O(n * log n + Q * log n)`. This is efficient enough for the given constraints.
Space
`O(n * log n)` to store the `up` table.
Trade-offs
Pros
Very fast query time, making it suitable for a large number of queries.
A standard and powerful technique applicable to many tree problems (like Lowest Common Ancestor).
Cons
Requires significant precomputation time and space compared to the brute-force approach.
More complex to understand and implement correctly.
Solutions
Solution
public class TreeAncestor { private int [][] p ; public TreeAncestor ( int n , int [] parent ) { p = new int [ n ][]; for ( int i = 0 ; i < n ; i ++) { p [ i ] = new int [ 18 ]; for ( int j = 0 ; j < 18 ; j ++) { p [ i ][ j ] = - 1 ; } } for ( int i = 0 ; i < n ; ++ i ) { p [ i ][ 0 ] = parent [ i ]; } for ( int j = 1 ; j < 18 ; ++ j ) { for ( int i = 0 ; i < n ; ++ i ) { if ( p [ i ][ j - 1 ] == - 1 ) { continue ; } p [ i ][ j ] = p [ p [ i ][ j - 1 ]][ j - 1 ]; } } } public int GetKthAncestor ( int node , int k ) { for ( int i = 17 ; i >= 0 ; -- i ) { if ((( k >> i ) & 1 ) == 1 ) { node = p [ node ][ i ]; if ( node == - 1 ) { break ; } } } return node ; } } /** * Your TreeAncestor object will be instantiated and called as such: * TreeAncestor obj = new TreeAncestor(n, parent); * int param_1 = obj.GetKthAncestor(node,k); */class TreeAncestor { private int [][] p ; public TreeAncestor ( int n , int [] parent ) { p = new int [ n ][ 18 ]; for ( var e : p ) { Arrays . fill ( e , - 1 ); } for ( int i = 0 ; i < n ; ++ i ) { p [ i ][ 0 ] = parent [ i ]; } for ( int j = 1 ; j < 18 ; ++ j ) { for ( int i = 0 ; i < n ; ++ i ) { if ( p [ i ][ j - 1 ] == - 1 ) { continue ; } p [ i ][ j ] = p [ p [ i ][ j - 1 ]][ j - 1 ]; } } } public int getKthAncestor ( int node , int k ) { for ( int i = 17 ; i >= 0 ; -- i ) { if ((( k >> i ) & 1 ) == 1 ) { node = p [ node ][ i ]; if ( node == - 1 ) { break ; } } } return node ; } } /** * Your TreeAncestor object will be instantiated and called as such: * TreeAncestor obj = new TreeAncestor(n, parent); * int param_1 = obj.getKthAncestor(node,k); */class TreeAncestor { public: TreeAncestor ( int n , vector < int >& parent ) { p = vector < vector < int >> ( n , vector < int > ( 18 , - 1 )); for ( int i = 0 ; i < n ; ++ i ) { p [ i ][ 0 ] = parent [ i ]; } for ( int j = 1 ; j < 18 ; ++ j ) { for ( int i = 0 ; i < n ; ++ i ) { if ( p [ i ][ j - 1 ] == - 1 ) { continue ; } p [ i ][ j ] = p [ p [ i ][ j - 1 ]][ j - 1 ]; } } } int getKthAncestor ( int node , int k ) { for ( int i = 17 ; ~ i ; -- i ) { if ( k >> i & 1 ) { node = p [ node ][ i ]; if ( node == - 1 ) { break ; } } } return node ; } private: vector < vector < int >> p ; }; /** * Your TreeAncestor object will be instantiated and called as such: * TreeAncestor* obj = new TreeAncestor(n, parent); * int param_1 = obj->getKthAncestor(node,k); */class TreeAncestor : def __init__ ( self , n : int , parent : List [ int ]): self . p = [[ - 1 ] * 18 for _ in range ( n )] for i , fa in enumerate ( parent ): self . p [ i ][ 0 ] = fa for j in range ( 1 , 18 ): for i in range ( n ): if self . p [ i ][ j - 1 ] == - 1 : continue self . p [ i ][ j ] = self . p [ self . p [ i ][ j - 1 ]][ j - 1 ] def getKthAncestor ( self , node : int , k : int ) -> int : for i in range ( 17 , - 1 , - 1 ): if k >> i & 1 : node = self . p [ node ][ i ] if node == - 1 : break return node # Your TreeAncestor object will be instantiated and called as such: # obj = TreeAncestor(n, parent) # param_1 = obj.getKthAncestor(node,k)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.