There is a binary tree rooted at 0
consisting of n
nodes. The nodes are labeled from 0
to n - 1
. You are given a 0-indexed integer array parents
representing the tree, where parents[i]
is the parent of node i
. Since node 0
is the root, parents[0] == -1
.
Each node has a score. To find the score of a node, consider if the node and the edges connected to it were removed. The tree would become one or more non-empty subtrees. The size of a subtree is the number of the nodes in it. The score of the node is the product of the sizes of all those subtrees.
Return the number of nodes that have the highest score.
Example 1:
Input: parents = [-1,2,0,2,0] Output: 3 Explanation: - The score of node 0 is: 3 * 1 = 3 - The score of node 1 is: 4 = 4 - The score of node 2 is: 1 * 1 * 2 = 2 - The score of node 3 is: 4 = 4 - The score of node 4 is: 4 = 4 The highest score is 4, and three nodes (node 1, node 3, and node 4) have the highest score.
Example 2:
Input: parents = [-1,2,0] Output: 2 Explanation: - The score of node 0 is: 2 = 2 - The score of node 1 is: 2 = 2 - The score of node 2 is: 1 * 1 = 1 The highest score is 2, and two nodes (node 0 and node 1) have the highest score.
Constraints:
n == parents.length
2 <= n <= 105
parents[0] == -1
0 <= parents[i] <= n - 1
for i != 0
parents
represents a valid binary tree.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:
Imagine a tree where each node has children. We need to calculate a score for each node by multiplying the sizes of the parts of the tree you get if you cut that node off. The brute force method is just figuring out the score for absolutely every node, one at a time.
Here's how the algorithm would work step-by-step:
def count_nodes_with_highest_score(parents):
number_of_nodes = len(parents)
children = [[] for _ in range(number_of_nodes)]
for node_index, parent_index in enumerate(parents):
if parent_index != -1:
children[parent_index].append(node_index)
scores = [0] * number_of_nodes
def calculate_subtree_size(node_index):
subtree_size = 1
for child_index in children[node_index]:
subtree_size += calculate_subtree_size(child_index)
return subtree_size
for node_index in range(number_of_nodes):
product = 1
#Calculate product of sizes of tree parts formed
for child_index in children[node_index]:
product *= calculate_subtree_size(child_index)
remaining_nodes = number_of_nodes - calculate_subtree_size(node_index)
if remaining_nodes > 0:
product *= remaining_nodes
if number_of_nodes == 1:
product = 1
scores[node_index] = product
max_score = 0
count = 0
# Count the nodes with the highest score
for score in scores:
if score > max_score:
max_score = score
count = 1
elif score == max_score:
count += 1
return count
To efficiently find the nodes with the highest score, we'll traverse the tree structure only once. For each node, we will calculate its score based on its children and parent, and keep track of the highest score seen so far and how many nodes have that score.
Here's how the algorithm would work step-by-step:
def count_highest_score_nodes(parents):
number_of_nodes = len(parents)
children = [[] for _ in range(number_of_nodes)]
for node_index, parent_index in enumerate(parents):
if parent_index != -1:
children[parent_index].append(node_index)
highest_score = 0
highest_score_node_count = 0
def calculate_subtree_size(node_index):
size = 1
for child_index in children[node_index]:
size += calculate_subtree_size(child_index)
subtree_sizes[node_index] = size
return size
subtree_sizes = [0] * number_of_nodes
calculate_subtree_size(0)
for node_index in range(number_of_nodes):
score = 1
# Calculate score based on children sizes
for child_index in children[node_index]:
score *= subtree_sizes[child_index]
# Consider the remaining nodes as well
remaining_nodes = number_of_nodes - subtree_sizes[node_index]
if parents[node_index] != -1:
score *= remaining_nodes
elif number_of_nodes > 1:
score = score
else:
score = 1
# Update the highest score and count
if score > highest_score:
highest_score = score
highest_score_node_count = 1
# Keep track if score matches the highest
elif score == highest_score:
highest_score_node_count += 1
return highest_score_node_count
Case | How to Handle |
---|---|
Null or empty parents array | Return 0 if the input 'parents' array is null or empty, as there are no nodes to process. |
Single node tree (parents array with only -1) | Return 1, as the single node will always have the highest score. |
Complete binary tree (or highly balanced tree) leading to potential stack overflow with recursion | Use iterative approach (e.g., using a stack or queue) instead of recursion to avoid potential stack overflow issues. |
Skewed tree (e.g., linked list structure) | The solution should handle skewed trees efficiently with a linear time complexity. |
Large number of nodes (close to the maximum constraint) | Ensure the data structures (e.g., arrays, maps) are sized appropriately to avoid memory issues, and use long data type for storing scores. |
All nodes point to root (parents array contains all -1 except for the root) | The algorithm should correctly compute scores where almost all nodes are directly connected to the root, accounting for disconnected subtrees correctly. |
Integer overflow when calculating score (product of node counts) | Use long data type to store the node counts and the product to prevent integer overflow during score calculation. |
Disconnected graph (multiple roots) | The code must handle multiple roots correctly by calculating the score for each and determining the maximum score among them. |