If we had a simpler tree where for edge ,
s[u] != s[v], then our problem would be finding the length of the longest path in the tree.
We can reduce the original problem to the above simpler problem by building an undirected graph having an edge only between different char’s. This is equivalent to deleting edges from the original tree having same char as endpoints.
DFS per leaf
In a tree, the longest path starts at some leaf — a node having 0 or 1 edge.
Because, say is the longest path and
is not a leaf. So,
has at least two edges. Say these two edges are:
and
. Say
is included in the path
. We can now have a longer path:
— producing a contradiction.
As a result, in our new graph, we can do DFS starting from each leaf and take the length of the longest path across the leaves.
A very wide tree like below will have many leaves and each DFS from a leaf will explore all edges.

Time: , space:
.
class Solution:
def build_graph(self, parent: List[int], s: str) -> Dict[int, Set[int]]:
adj_list = { u : set() for u in range( len(parent) ) }
for u, v in enumerate(parent[1:], start=1):
if s[u] == s[v]:
continue
adj_list[u].add(v)
adj_list[v].add(u)
return adj_list
def max_len_after(self, u, adj_list, visited) -> int:
visited.add(u)
max_len = 0
for v in adj_list[u]:
if v in visited:
continue
max_len = max( max_len, 1 + self.max_len_after(v, adj_list, visited) )
return max_len
def longestPath(self, parent: List[int], s: str) -> int:
adj_list = self.build_graph(parent, s)
leaves = [u for u, edges in adj_list.items() if len(edges) <= 1]
max_len = 0
for u in leaves:
max_len = max( max_len, 1 + self.max_len_after(u, adj_list, set()) )
return max_len
DFS per connected component
Note, the original tree is a single connected component. Since a tree is connected and there are minimal, only edges between
nodes, deleting every edge increases count of connected components by 1. So, our new graph is a forest of trees. No edges flow between the connected components. As long as we can find the longest path length for one tree with a single DFS, we can run DFS per connected component and take the max length across them. Total time will be linear in the size of the forest.
In a tree, longest path can be of two types: (1) chain (2) arc. We can take max between these two types. Note, an arc-type path including as its root, cannot contribute to a longest path that includes
‘s parent.

For a single connected component, we can run DFS once to find both types of maxes in the following way.
Say, represents chain-type longest path length of a tree that is rooted at
. Then,
.
Say represents arc-type longest path length of a tree that is rooted at
and
represents arc-type longest path length including
. Note,
may not include
. Then,
and
.
Max length for a connected component (cc) is then .
Time: , space:
.
class Solution:
def build_graph(self, parent: List[int], s: str) -> Dict[int, Set[int]]:
adj_list = { u : set() for u in range( len(parent) ) }
for u, v in enumerate(parent[1:], start=1):
if s[u] == s[v]:
continue
adj_list[u].add(v)
adj_list[v].add(u)
return adj_list
def tree_max(self, root, adj_list, visited) -> Tuple[int, int]:
visited.add(root)
child_chain_max, child_chain_second_max = 0, 0
child_arc_max = 0
for v in adj_list[root]:
if v in visited:
continue
child_chain, child_arc = self.tree_max(v, adj_list, visited)
child_arc_max = max(child_arc_max, child_arc)
if child_chain < child_chain_second_max:
continue
if child_chain > child_chain_max:
child_chain_max, child_chain_second_max = child_chain, child_chain_max
else:
child_chain_second_max = child_chain
root_chain_max = 1 + child_chain_max
root_arc_max = 1 + child_chain_max + child_chain_second_max
root_global_arc_max = max(child_arc_max, root_arc_max)
return root_chain_max, root_global_arc_max
def longestPath(self, parent: List[int], s: str) -> int:
adj_list = self.build_graph(parent, s)
visited = set()
max_len = 0
for u in adj_list:
if u in visited:
continue
# Another connected component
cc_chain_max, cc_arc_max = self.tree_max(u, adj_list, visited)
max_len = max(max_len, cc_chain_max, cc_arc_max)
return max_len
Since it is a tree and we are going downwards from root, we can use a directed graph with parent-to-child edges only. The adjacency list takes up half the space. We also do not need to skip visited nodes within a DFS. However, to skip exploring a connected component more than once, we still keep visited in the outer loop.
class Solution:
def build_graph(self, parent: List[int], s: str) -> Dict[int, Set[int]]:
adj_list = { u : set() for u in range( len(parent) ) }
for c, p in enumerate(parent[1:], start=1):
if s[c] == s[p]:
continue
adj_list[p].add(c)
return adj_list
def tree_max(self, root, adj_list, visited) -> Tuple[int, int]:
visited.add(root)
child_chain_max, child_chain_second_max = 0, 0
child_arc_max = 0
for v in adj_list[root]:
child_chain, child_arc = self.tree_max(v, adj_list, visited)
child_arc_max = max(child_arc_max, child_arc)
if child_chain < child_chain_second_max:
continue
if child_chain > child_chain_max:
child_chain_max, child_chain_second_max = child_chain, child_chain_max
else:
child_chain_second_max = child_chain
root_chain_max = 1 + child_chain_max
root_arc_max = 1 + child_chain_max + child_chain_second_max
root_global_arc_max = max(child_arc_max, root_arc_max)
return root_chain_max, root_global_arc_max
def longestPath(self, parent: List[int], s: str) -> int:
adj_list = self.build_graph(parent, s)
visited = set()
max_len = 0
for u in adj_list:
if u in visited:
continue
# Another connected component
cc_chain_max, cc_arc_max = self.tree_max(u, adj_list, visited)
max_len = max(max_len, cc_chain_max, cc_arc_max)
return max_len
Leave a comment