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 the kth ancestor of the given node node. 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] < n for all 0 < i < n0 <= node < n5 * 104 queries.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
To find the Kth ancestor of a node, the most straightforward approach is to simply walk up the tree one step at a time. We essentially climb up the tree from the given node until we've moved 'K' steps upwards.
Here's how the algorithm would work step-by-step:
def kth_ancestor_brute_force(node, k, parent_map):
steps_taken = 0
current_node = node
# Traverse upwards until k steps are taken
while steps_taken < k:
# If we reach the root, the ancestor does not exist
if current_node not in parent_map:
return None
current_node = parent_map[current_node]
steps_taken += 1
return current_nodeTo efficiently find a node's ancestor that is 'k' levels above it in a tree, we use a precomputed table. This table allows us to jump up multiple levels at once, significantly reducing the number of steps compared to going up one level at a time.
Here's how the algorithm would work step-by-step:
class TreeAncestor:
def __init__(self, number_of_nodes: int, parent: list[int]):
self.max_jump = number_of_nodes.bit_length()
self.ancestors = [([0] * number_of_nodes) for _ in range(self.max_jump)]
# Initialize the first level of ancestors (immediate parents).
for node in range(number_of_nodes):
self.ancestors[0][node] = parent[node]
# Build the ancestor table for higher powers of 2.
for jump in range(1, self.max_jump):
for node in range(number_of_nodes):
intermediate_ancestor = self.ancestors[jump - 1][node]
if intermediate_ancestor != -1:
self.ancestors[jump][node] = self.ancestors[jump - 1][intermediate_ancestor]
else:
self.ancestors[jump][node] = -1
def getKthAncestor(self, node: int, distance: int) -> int:
# Iterate through the powers of 2 to find the kth ancestor.
for jump in range(self.max_jump):
# If the current power of 2 is needed to reach k.
if (distance >> jump) & 1:
node = self.ancestors[jump][node]
# If we reach the root, return -1.
if node == -1:
return -1
return node| Case | How to Handle |
|---|---|
| Null or empty tree | Return null or appropriate error if the tree is null since there are no nodes to find ancestors of. |
| k is zero | Return the node itself as the 0th ancestor of a node is the node itself. |
| k is larger than the height of the tree from the given node | Return null because the node does not have a kth ancestor at this depth. |
| Node is null | Return null since there's no node to find ancestors of. |
| Node is the root, and k is greater than 0 | Return null since the root has no ancestor. |
| k is a very large number, potentially leading to integer overflow if not handled carefully | Use a data type large enough to hold potential values of k, or implement a check to prevent arbitrarily large k. |
| Tree with only one node | If k > 0 return null since there are no ancestors, else if k = 0 return the node itself. |
| Skewed tree (e.g., a linked list) | The algorithm might need to traverse a long chain of nodes which could impact performance; consider binary lifting optimization. |