AlgoMaster Logo

Longest Path With Different Adjacent Characters

hardFrequencyUpdated September 21, 2026

Understanding the Problem

We are given a rooted tree where each node has a label (a character). We need to find the longest path in this tree such that no two adjacent nodes along the path share the same character. The answer is the number of nodes in this path, not the number of edges.

The tree is given as a parent array, where parent[i] is the parent of node i, and node 0 is the root (its parent is -1). The first step is building an adjacency list from this array.

The path can go through any part of the tree. It does not need to start or end at the root, and it does not need to go strictly downward. A path can travel upward through a node and back down to another child, forming an inverted-V shape. But a path cannot branch, so at any given node, the path can extend through at most two of its children.

This is structurally similar to the "diameter of a tree" problem. At each node, we compute the two longest downward chains (extending through children with different characters), combine them through the current node to get a candidate path, and report the single best chain upward for the parent to use.

Key Constraints:

  • 1 <= n <= 10^5 -- With n up to 100,000, an O(n^2) approach performs on the order of 10^10 operations and will time out. We need O(n).
  • parent represents a valid tree -- No cycles, connected. We can build an adjacency list and do a single DFS or BFS traversal.
  • parent[0] == -1 -- Node 0 is always the root, so we can root the traversal there.

Approach 1: Brute Force (DFS from Every Node)

Intuition

For every node in the tree, start a DFS and explore all paths originating from that node, tracking the longest one where no two adjacent nodes share the same character.

From any starting node, we explore all neighbors. For each neighbor that has a different character, we extend the path and continue recursively. We avoid revisiting the node we came from; since this is a tree with no cycles, that single check is enough to prevent loops.

For each starting node, the DFS visits up to O(n) nodes, and we repeat this for all n nodes, giving O(n^2) total work. This recomputes the same sub-chains from scratch on every restart, which is the source of the inefficiency.

Algorithm

  1. Build an adjacency list from the parent array.
  2. For each node i from 0 to n-1, run a DFS that explores all valid paths starting from i.
  3. In the DFS, track the current path length. At each step, try all neighbors. If a neighbor has a different character from the current node and is not the node we came from, recurse with path length + 1.
  4. Update the global maximum whenever the current path length exceeds it.
  5. Return the global maximum.

Code

This is too slow for n up to 10^5. The next approach computes the longest valid chain from each node exactly once, bottom-up, bringing the cost down to O(n).

Approach 2: DFS with Post-Order Aggregation (Optimal)

Intuition

This is the tree-diameter technique adapted for the character constraint. Instead of launching a DFS from every node, we do a single post-order DFS from the root. At each node, we compute the longest chain of nodes (with adjacent characters differing) extending downward through any of its children, and we return that chain length to the parent so it can build on it.

A path passing through a node can extend into at most two of its children. So the longest path through a node equals 1 (for itself) plus the two longest valid child chains. When we report upward to the parent, we can extend in only one direction, so we return 1 plus the single longest child chain.

A child chain counts as valid only when the child has a different character from the current node. If child and parent share the same character, the edge between them cannot be part of any valid path, so that child contributes 0 to the current node's chains.

Algorithm

  1. Build a children list from the parent array (we only need parent-to-child edges since we will DFS from the root downward).
  2. Start a post-order DFS from node 0 (the root).
  3. At each node, initialize variables to track the two longest valid chains from children.
  4. For each child: if s[child] != s[node], the chain from that child is valid. Track the top two.
  5. The path through this node has length 1 + longest + secondLongest. Update the global maximum.
  6. Return 1 + longest to the parent (the best single chain extending downward from this node).
  7. If no child has a different character, the chain length from this node is 1 (the node alone).

Example Walkthrough

1Tree with s="abacbe". Post-order DFS: process leaves first, then parents. maxPath=1
0 (a)root1 (b)2 (a)3 (c)4 (b)5 (e)
1/8

Code

On a deeply skewed tree, the recursion depth reaches O(n), which can overflow the call stack. The next approach keeps the same O(n) time but processes nodes bottom-up with an explicit queue instead of recursion.

Approach 3: Topological Sort (BFS, Iterative)

Intuition

Instead of DFS recursion, we process the tree bottom-up with a topological sort. We start from the leaf nodes (which have no children) and work up toward the root.

The computation mirrors Approach 2, but the traversal uses BFS from the leaves. Each leaf has a chain length of 1. Once all children of a node have been processed, we combine the two longest valid child chains (those where the child character differs from the parent), update the global max, and propagate the single best chain upward.

We track how many children each node still has unprocessed. When that count reaches zero, the node is ready to process, and its chain value propagates to its parent. This is Kahn's algorithm applied to a tree.

Algorithm

  1. Build the children list and compute the number of children for each node.
  2. Initialize a queue with all leaf nodes (nodes with 0 children). Each leaf has chain length 1.
  3. While the queue is not empty, dequeue a node. If its parent exists and has a different character, offer the chain value to the parent. Track the top two chain values at each parent.
  4. Decrement the parent's remaining child count. When it hits 0, compute the path through the parent (1 + top two chains), update the global max, and enqueue the parent.
  5. Return the global max.

Example Walkthrough

1Init: find leaves (childCount=0). Queue=[3,4,5]. maxPath=1
0 (a)1 (b)2 (a)3 (c)leaf4 (b)leaf5 (e)leaf
1/8

Code