LeetCode 3004 - Maximum Subtree of the Same Color
We are given a rooted tree with n nodes, where node 0 is the root. The input edges describes the tree structure. Each entry edges[i] = [u, v] indicates that there is an undirected edge between nodes u and v.
Difficulty: 🟡 Medium
Topics: Array, Dynamic Programming, Tree, Depth-First Search
Solution
Problem Understanding
We are given a rooted tree with n nodes, where node 0 is the root.
The input edges describes the tree structure. Each entry edges[i] = [u, v] indicates that there is an undirected edge between nodes u and v. Since the problem guarantees that the graph is a tree rooted at node 0, every node belongs to exactly one subtree rooted somewhere in the tree.
We are also given an array colors, where colors[i] represents the color assigned to node i.
The goal is to find a node v such that every node inside the subtree rooted at v has exactly the same color. Among all such valid subtrees, we must return the size of the largest one.
A subtree rooted at node v contains:
- Node
vitself. - Every descendant of
v.
For a subtree to be valid, every node inside it must share one identical color.
The constraints are important:
ncan be as large as50,000.- A subtree can also contain up to
50,000nodes. - Colors can be large values (
<= 100000), so color values themselves cannot be used as array indices in a compact DP table. - Since the tree is large, repeatedly traversing subtrees would be too expensive.
These constraints strongly suggest that we need a single traversal of the tree, most naturally a Depth-First Search (DFS).
Some important edge cases include:
- A tree consisting of only one node. The answer must be
1. - A tree where every node has the same color. The answer is the size of the entire tree.
- A tree where every parent has children of different colors. In that case, only leaf nodes form valid same-color subtrees, so the answer is
1. - Deep chains of nodes where recursion depth could become large in Python.
- Internal nodes whose descendants are all the same color but differ from the root of that subtree. Such subtrees are not valid because every node, including the root, must share the same color.
Approaches
Brute Force
A straightforward approach would be to examine every node as a potential subtree root.
For each node:
- Traverse its entire subtree.
- Collect all colors appearing in that subtree.
- Check whether all colors are identical.
- If yes, update the answer with the subtree size.
This approach is correct because it explicitly verifies the condition for every subtree.
However, it is extremely inefficient. Consider a chain of n nodes. The subtree of the root contains n nodes, the next subtree contains n-1, and so on.
The total work becomes:
$$n + (n-1) + (n-2) + \cdots + 1 = O(n^2)$$
With n = 50,000, this is far too slow.
Key Insight
A subtree is monochromatic if and only if:
- Every child subtree is monochromatic.
- Every child subtree has the same color as the current node.
This observation allows us to process the tree bottom-up.
During a DFS, each node can return:
- Whether its subtree is monochromatic.
- What color that monochromatic subtree has.
- The size of the subtree.
Leaf nodes are automatically monochromatic.
For an internal node:
- If any child subtree is not monochromatic, the current subtree cannot be monochromatic.
- If a child subtree has a different color from the current node, the current subtree cannot be monochromatic.
- Otherwise, the current subtree is monochromatic.
Since each edge is processed exactly once, we obtain a linear-time solution.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(n) | Repeatedly traverses many overlapping subtrees |
| Optimal DFS | O(n) | O(n) | Processes each node once using postorder traversal |
Algorithm Walkthrough
Optimal DFS Strategy
- Build an adjacency list from the edge list.
Since the input describes a tree, an adjacency list allows efficient traversal of neighboring nodes.
2. Start a DFS from the root node 0.
We perform a postorder traversal because a node's validity depends on the results of its children. 3. For each node, initialize:
subtree_size = 1same_color = True
Every node contributes itself to its subtree. 4. Visit each child.
Skip the parent node to avoid traversing backward in the undirected tree. 5. Recursively process the child.
The child returns:
- Whether its subtree is monochromatic.
- Its subtree size.
- Add the child's size to the current subtree size.
This allows us to compute subtree sizes in one traversal. 7. Verify the monochromatic condition.
If the child subtree is not monochromatic, mark the current subtree as invalid.
If the child's color differs from the current node's color, mark the current subtree as invalid. 8. After processing all children:
- If the subtree remains valid, update the global answer using its size.
- Return that this subtree is monochromatic.
- Otherwise return that it is not monochromatic.
- Continue until the DFS finishes at the root.
- Return the maximum valid subtree size encountered.
Why it works
The algorithm processes nodes in postorder, meaning every child subtree is completely analyzed before its parent. A subtree rooted at node u is monochromatic exactly when every child subtree is monochromatic and all those child subtrees share the same color as u. Because these conditions are both necessary and sufficient, every subtree is classified correctly. Since every node is visited once, the largest valid subtree is found during the traversal.
Python Solution
from typing import List
import sys
class Solution:
def maximumSubtreeSize(self, edges: List[List[int]], colors: List[int]) -> int:
n = len(colors)
sys.setrecursionlimit(100000)
graph = [[] for _ in range(n)]
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
answer = 1
def dfs(node: int, parent: int):
nonlocal answer
subtree_size = 1
is_monochromatic = True
for neighbor in graph[node]:
if neighbor == parent:
continue
child_mono, child_size = dfs(neighbor, node)
subtree_size += child_size
if not child_mono:
is_monochromatic = False
if colors[neighbor] != colors[node]:
is_monochromatic = False
if is_monochromatic:
answer = max(answer, subtree_size)
return is_monochromatic, subtree_size
dfs(0, -1)
return answer
The implementation first constructs an adjacency list representation of the tree.
The DFS returns two pieces of information:
- Whether the subtree rooted at the current node is monochromatic.
- The size of that subtree.
Each node starts as a valid monochromatic subtree of size one. As children are processed, their subtree sizes are accumulated.
The critical observation is that if a child subtree is monochromatic and has the same color as the current node, then every node inside that child subtree must also have the same color as the current node. Therefore checking the child's root color is sufficient.
Whenever a node's subtree satisfies the monochromatic condition, the global answer is updated.
Because every edge is traversed exactly once, the algorithm runs in linear time.
Go Solution
func maximumSubtreeSize(edges [][]int, colors []int) int {
n := len(colors)
graph := make([][]int, n)
for _, e := range edges {
u := e[0]
v := e[1]
graph[u] = append(graph[u], v)
graph[v] = append(graph[v], u)
}
answer := 1
var dfs func(int, int) (bool, int)
dfs = func(node int, parent int) (bool, int) {
subtreeSize := 1
isMonochromatic := true
for _, neighbor := range graph[node] {
if neighbor == parent {
continue
}
childMono, childSize := dfs(neighbor, node)
subtreeSize += childSize
if !childMono {
isMonochromatic = false
}
if colors[neighbor] != colors[node] {
isMonochromatic = false
}
}
if isMonochromatic {
if subtreeSize > answer {
answer = subtreeSize
}
}
return isMonochromatic, subtreeSize
}
dfs(0, -1)
return answer
}
The Go solution mirrors the Python implementation almost exactly.
The adjacency list is represented using a slice of slices. The DFS closure returns both the monochromatic status and subtree size. Since the answer is maintained globally, there is no need to return it through recursion.
Integer overflow is not a concern because subtree sizes are at most 50,000, which easily fits into Go's int.
Worked Examples
Example 1
edges = [[0,1],[0,2],[0,3]]
colors = [1,1,2,3]
Tree:
0(1)
/ | \
1(1) 2(2) 3(3)
DFS processing order:
| Node | Subtree Size | Monochromatic |
|---|---|---|
| 1 | 1 | True |
| 2 | 1 | True |
| 3 | 1 | True |
Current answer becomes 1.
Now process node 0.
| Child | Child Color | Parent Color | Match |
|---|---|---|---|
| 1 | 1 | 1 | Yes |
| 2 | 2 | 1 | No |
| 3 | 3 | 1 | No |
Node 0 is not monochromatic.
Final answer:
1
Example 2
edges = [[0,1],[0,2],[0,3]]
colors = [1,1,1,1]
Tree:
0
/ | \
1 2 3
Leaves:
| Node | Size | Valid |
|---|---|---|
| 1 | 1 | True |
| 2 | 1 | True |
| 3 | 1 | True |
Process node 0.
| Child | Child Color | Parent Color |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 1 |
| 3 | 1 | 1 |
All conditions hold.
Subtree size:
1 + 1 + 1 + 1 = 4
Answer becomes:
4
Example 3
edges = [[0,1],[0,2],[2,3],[2,4]]
colors = [1,2,3,3,3]
Tree:
0(1)
/ \
1(2) 2(3)
/ \
3(3) 4(3)
Process leaves first.
| Node | Size | Valid |
|---|---|---|
| 1 | 1 | True |
| 3 | 1 | True |
| 4 | 1 | True |
Process node 2.
| Child | Color |
|---|---|
| 3 | 3 |
| 4 | 3 |
Both match node 2's color.
Subtree size:
1 + 1 + 1 = 3
Answer becomes 3.
Process node 0.
Child colors:
2 and 3
Neither matches color 1.
Node 0 is invalid.
Final answer:
3
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Every node and edge is processed once |
| Space | O(n) | Adjacency list and recursion stack |
The DFS performs a single traversal of the tree. Since a tree with n nodes contains exactly n - 1 edges, every edge is visited only once in each direction. The adjacency list requires O(n) space, and in the worst case a skewed tree can produce a recursion depth of O(n).
Test Cases
from typing import List
s = Solution()
assert s.maximumSubtreeSize(
[[0,1],[0,2],[0,3]],
[1,1,2,3]
) == 1 # provided example 1
assert s.maximumSubtreeSize(
[[0,1],[0,2],[0,3]],
[1,1,1,1]
) == 4 # provided example 2
assert s.maximumSubtreeSize(
[[0,1],[0,2],[2,3],[2,4]],
[1,2,3,3,3]
) == 3 # provided example 3
assert s.maximumSubtreeSize(
[],
[7]
) == 1 # single node tree
assert s.maximumSubtreeSize(
[[0,1]],
[1,2]
) == 1 # two different colors
assert s.maximumSubtreeSize(
[[0,1]],
[5,5]
) == 2 # entire tree valid
assert s.maximumSubtreeSize(
[[0,1],[1,2],[2,3]],
[1,1,1,1]
) == 4 # chain with same color
assert s.maximumSubtreeSize(
[[0,1],[1,2],[2,3]],
[1,2,3,4]
) == 1 # chain with all different colors
assert s.maximumSubtreeSize(
[[0,1],[0,2],[1,3],[1,4]],
[2,2,2,2,2]
) == 5 # entire tree monochromatic
assert s.maximumSubtreeSize(
[[0,1],[0,2],[2,3],[2,4]],
[1,1,2,2,2]
) == 3 # largest valid subtree is internal node
Test Summary
| Test | Why |
|---|---|
| Example 1 | Multiple colors at root |
| Example 2 | Entire tree has same color |
| Example 3 | Largest valid subtree is internal |
| Single node | Smallest possible input |
| Two nodes, different colors | No subtree larger than 1 |
| Two nodes, same color | Whole tree valid |
| Uniform chain | Deep monochromatic subtree |
| Different-color chain | Every valid subtree is a leaf |
| Entire tree same color | Large valid root subtree |
| Internal maximum subtree | Ensures non-root answer is found |
Edge Cases
Single Node Tree
When n = 1, there are no edges and the only subtree is the node itself. A common bug is assuming every node has children or failing to initialize the answer properly. The implementation initializes answer = 1, so this case is handled naturally.
Entire Tree Has One Color
If every node has the same color, the correct answer is the size of the entire tree. Some implementations only verify immediate children and forget that deeper descendants must also be checked. Our DFS guarantees that every descendant subtree is validated before its parent, so the root is correctly recognized as a monochromatic subtree.
Largest Valid Subtree Is Not the Root
Many trees contain a mixed-color root but a large monochromatic subtree deeper in the tree. An implementation that only checks the root would fail. The DFS evaluates every node independently and updates the global maximum whenever a valid subtree is found, ensuring internal subtrees are considered.
Deep Skewed Tree
A tree may degenerate into a linked list of length 50,000. Recursive DFS can exceed Python's default recursion limit. The implementation explicitly increases the recursion limit with sys.setrecursionlimit(100000), preventing recursion depth failures on valid inputs.
Mixed Valid and Invalid Child Subtrees
A node may have several children where some subtrees are monochromatic and others are not. A buggy implementation might stop after finding one valid child. Our DFS requires every child subtree to be monochromatic and every child root color to match the current node's color. If any child violates these conditions, the current subtree is marked invalid. This exactly matches the problem definition.