3373. Maximize the Number of Target Nodes After Connecting Trees II
Problem Description
You are given two undirected trees with n
and m
nodes respectively. The first tree has nodes labeled from [0, n-1]
and the second tree has nodes labeled from [0, m-1]
.
The trees are represented by two 2D arrays:
edges1
of lengthn-1
representing edges in the first tree, whereedges1[i] = [ai, bi]
means there's an edge between nodesai
andbi
edges2
of lengthm-1
representing edges in the second tree, whereedges2[i] = [ui, vi]
means there's an edge between nodesui
andvi
A key concept in this problem is the "target" relationship: Node u
is target to node v
if the number of edges on the path from u
to v
is even. Note that every node is always target to itself (0 edges, which is even).
Your task is to determine, for each node i
in the first tree, what is the maximum possible number of nodes that can be target to node i
if you connect exactly one node from the first tree to one node from the second tree. This connection creates a combined graph where you need to count target nodes.
The output should be an array of n
integers where answer[i]
represents the maximum possible number of target nodes for node i
in the first tree.
Important: Each query is independent - after calculating the answer for one node, the added edge is removed before processing the next node.
The solution leverages the fact that nodes at even distances from a reference point will have the same parity (both even or both odd depths), and connecting trees optimally means choosing connections that maximize the number of nodes with matching parity relationships.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The problem involves two undirected trees, which are special types of graphs. We have nodes and edges explicitly defined in
edges1
andedges2
.
Is it a tree?
- Yes: The problem explicitly states we're working with two undirected trees. A tree is a connected acyclic graph with
n
nodes andn-1
edges.
DFS
- Yes: We arrive at DFS as the recommended approach for tree problems.
Why DFS is appropriate for this problem:
-
Tree Traversal: We need to traverse both trees to understand their structure and calculate distances between nodes.
-
Distance Parity Calculation: The problem requires determining whether paths have even or odd lengths. DFS allows us to track the depth/distance of each node from a starting point, which directly relates to path parity.
-
Bipartite-like Classification: The solution uses DFS to classify nodes into two groups based on their depth parity (even or odd depth from root). This is similar to bipartite graph coloring, where DFS is the standard approach.
-
Efficient Tree Exploration: DFS efficiently explores all nodes in both trees exactly once, calculating the required parity information in O(n) and O(m) time respectively.
Conclusion: The flowchart correctly leads us to DFS for this tree-based problem. The DFS pattern is used to:
- Traverse each tree and assign parity values (0 or 1) to nodes based on their depth
- Count how many nodes have each parity value in both trees
- Use these counts to determine the maximum number of target nodes for each node in the first tree
Intuition
Let's think about what happens when we connect two trees. When we add an edge between node x
from tree 1 and node y
from tree 2, we create paths between all nodes in both trees.
The key insight is understanding the "target" relationship - node u
is target to node v
if the path between them has an even number of edges. This is essentially asking: which nodes are at even distances from each other?
In a tree, we can classify nodes into two groups based on their distance parity from any reference node. If we pick node 0 as reference and do a DFS:
- Nodes at even distances (0, 2, 4, ...) form one group
- Nodes at odd distances (1, 3, 5, ...) form another group
Here's the crucial observation: within a single tree, all nodes in the same parity group are at even distances from each other, and nodes in different groups are at odd distances from each other.
When we connect two trees by adding an edge between node i
from tree 1 and node j
from tree 2:
- If the edge connects nodes of the same parity (both even or both odd depth), then nodes of the same parity across both trees will be at even distances
- If the edge connects nodes of different parity, then nodes of opposite parity across trees will be at even distances
For any node i
in tree 1, the target nodes come from:
- Nodes in tree 1 that have the same parity as node
i
(these are always at even distance regardless of how we connect to tree 2) - Nodes from tree 2 that we can make "target" by choosing the right connection point
To maximize the total, we should connect to tree 2 in a way that adds the maximum possible nodes from tree 2. Since tree 2 has two parity groups, we want to connect such that the larger group becomes target to node i
.
This leads to our approach:
- Use DFS to classify nodes in both trees by parity (0 or 1)
- Count how many nodes belong to each parity group in both trees
- For each node
i
in tree 1, the answer is: (nodes with same parity asi
in tree 1) + (maximum parity group size in tree 2)
The beauty of this solution is that it reduces a complex path-counting problem to a simple parity classification problem using DFS.
Learn more about Tree, Depth-First Search and Breadth-First Search patterns.
Solution Approach
The solution implements the parity-based approach using DFS to classify nodes in both trees.
Step 1: Build Adjacency Lists
The build
function converts the edge lists into adjacency list representations for both trees:
def build(edges: List[List[int]]) -> List[List[int]]:
n = len(edges) + 1 # [Tree](/problems/tree_intro) has n-1 edges for n nodes
g = [[] for _ in range(n)]
for a, b in edges:
g[a].append(b)
g[b].append(a)
return g
Step 2: DFS for Parity Classification
The dfs
function traverses the tree and assigns parity values to each node:
def dfs(g: List[List[int]], a: int, fa: int, c: List[int], d: int, cnt: List[int]):
c[a] = d # Assign parity value (0 or 1) to current node
cnt[d] += 1 # Increment count for this parity group
for b in g[a]:
if b != fa: # Avoid going back to parent
dfs(g, b, a, c, d ^ 1, cnt) # Flip parity for child (0→1 or 1→0)
Key parameters:
g
: adjacency list of the treea
: current nodefa
: parent node (to avoid cycles)c
: array storing parity value for each noded
: current depth parity (0 or 1)cnt
: array counting nodes in each parity group
Step 3: Main Algorithm
g1 = build(edges1) # Build adjacency list for [tree](/problems/tree_intro) 1
g2 = build(edges2) # Build adjacency list for tree 2
n, m = len(g1), len(g2)
c1 = [0] * n # Parity values for [tree](/problems/tree_intro) 1 nodes
c2 = [0] * m # Parity values for tree 2 nodes
cnt1 = [0, 0] # Count of nodes with parity 0 and 1 in tree 1
cnt2 = [0, 0] # Count of nodes with parity 0 and 1 in tree 2
dfs(g2, 0, -1, c2, 0, cnt2) # Classify [tree](/problems/tree_intro) 2 nodes
dfs(g1, 0, -1, c1, 0, cnt1) # Classify tree 1 nodes
Step 4: Calculate Results
t = max(cnt2) # Maximum parity group size in [tree](/problems/tree_intro) 2
return [t + cnt1[c1[i]] for i in range(n)]
For each node i
in tree 1:
c1[i]
gives its parity (0 or 1)cnt1[c1[i]]
gives the count of nodes with the same parity in tree 1t
is the maximum possible nodes we can get from tree 2- Total target nodes = nodes from tree 1 with same parity + maximum from tree 2
Time Complexity: O(n + m) where n and m are the sizes of the two trees. We perform DFS once on each tree.
Space Complexity: O(n + m) for storing the adjacency lists and parity arrays.
The elegance of this solution lies in recognizing that the problem reduces to counting parity groups, transforming a complex graph distance problem into a simple counting problem using DFS.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Example Input:
- Tree 1: edges1 = [[0,1], [0,2], [2,3], [2,4]] (5 nodes)
- Tree 2: edges2 = [[0,1], [0,2], [0,3]] (4 nodes)
Tree 1: Tree 2: 0 1 / \ | 1 2 0 / \ / \ 3 4 2 3
Step 1: Build Adjacency Lists
- g1: {0: [1,2], 1: [0], 2: [0,3,4], 3: [2], 4: [2]}
- g2: {0: [1,2,3], 1: [0], 2: [0], 3: [0]}
Step 2: DFS for Parity Classification
For Tree 2 (starting from node 0 with parity 0):
- Node 0: parity = 0 (depth 0)
- Node 1: parity = 1 (depth 1)
- Node 2: parity = 1 (depth 1)
- Node 3: parity = 1 (depth 1)
- Result: c2 = [0,1,1,1], cnt2 = [1,3] (1 node with parity 0, 3 nodes with parity 1)
For Tree 1 (starting from node 0 with parity 0):
- Node 0: parity = 0 (depth 0)
- Node 1: parity = 1 (depth 1)
- Node 2: parity = 1 (depth 1)
- Node 3: parity = 0 (depth 2)
- Node 4: parity = 0 (depth 2)
- Result: c1 = [0,1,1,0,0], cnt1 = [3,2] (3 nodes with parity 0, 2 nodes with parity 1)
Step 3: Calculate Maximum from Tree 2
- t = max(cnt2) = max(1,3) = 3
Step 4: Calculate Results for Each Node
For node 0 in Tree 1:
- c1[0] = 0 (parity 0)
- Nodes in Tree 1 with same parity: cnt1[0] = 3 (nodes 0,3,4)
- Maximum from Tree 2: t = 3
- answer[0] = 3 + 3 = 6
For node 1 in Tree 1:
- c1[1] = 1 (parity 1)
- Nodes in Tree 1 with same parity: cnt1[1] = 2 (nodes 1,2)
- Maximum from Tree 2: t = 3
- answer[1] = 2 + 3 = 5
For node 2 in Tree 1:
- c1[2] = 1 (parity 1)
- Nodes in Tree 1 with same parity: cnt1[1] = 2 (nodes 1,2)
- Maximum from Tree 2: t = 3
- answer[2] = 2 + 3 = 5
For node 3 in Tree 1:
- c1[3] = 0 (parity 0)
- Nodes in Tree 1 with same parity: cnt1[0] = 3 (nodes 0,3,4)
- Maximum from Tree 2: t = 3
- answer[3] = 3 + 3 = 6
For node 4 in Tree 1:
- c1[4] = 0 (parity 0)
- Nodes in Tree 1 with same parity: cnt1[0] = 3 (nodes 0,3,4)
- Maximum from Tree 2: t = 3
- answer[4] = 3 + 3 = 6
Final Answer: [6, 5, 5, 6, 6]
Why This Works:
- When we want to maximize target nodes for node i, we need nodes at even distances from i
- In Tree 1, nodes with the same parity as i are always at even distances (they're "free" targets)
- From Tree 2, we can choose to connect in a way that makes either parity group 0 or group 1 be at even distances from node i
- Since we want to maximize, we always choose the larger group from Tree 2 (which is 3 nodes with parity 1)
- The total is the sum of same-parity nodes in Tree 1 plus the maximum group from Tree 2
Solution Implementation
1class Solution:
2 def maxTargetNodes(
3 self, edges1: List[List[int]], edges2: List[List[int]]
4 ) -> List[int]:
5 def build_adjacency_list(edges: List[List[int]]) -> List[List[int]]:
6 """Build adjacency list representation of a tree from edge list."""
7 num_nodes = len(edges) + 1
8 adjacency_list = [[] for _ in range(num_nodes)]
9
10 for node_a, node_b in edges:
11 adjacency_list[node_a].append(node_b)
12 adjacency_list[node_b].append(node_a)
13
14 return adjacency_list
15
16 def dfs_color_nodes(
17 graph: List[List[int]],
18 current_node: int,
19 parent_node: int,
20 node_colors: List[int],
21 current_color: int,
22 color_counts: List[int]
23 ) -> None:
24 """
25 DFS to color nodes with alternating colors (0 or 1) based on distance parity.
26 Also counts nodes of each color.
27
28 Args:
29 graph: Adjacency list of the tree
30 current_node: Current node being processed
31 parent_node: Parent of current node (-1 if root)
32 node_colors: Array to store color of each node
33 current_color: Color to assign to current node (0 or 1)
34 color_counts: Array to count nodes of each color
35 """
36 # Assign color to current node
37 node_colors[current_node] = current_color
38 # Increment count for this color
39 color_counts[current_color] += 1
40
41 # Visit all neighbors except parent
42 for neighbor in graph[current_node]:
43 if neighbor != parent_node:
44 # Recursively color children with opposite color (XOR with 1 flips 0->1, 1->0)
45 dfs_color_nodes(
46 graph,
47 neighbor,
48 current_node,
49 node_colors,
50 current_color ^ 1,
51 color_counts
52 )
53
54 # Build adjacency lists for both trees
55 graph1 = build_adjacency_list(edges1)
56 graph2 = build_adjacency_list(edges2)
57
58 # Get number of nodes in each tree
59 num_nodes_tree1 = len(graph1)
60 num_nodes_tree2 = len(graph2)
61
62 # Initialize color arrays for both trees (0 or 1 based on distance parity from root)
63 colors_tree1 = [0] * num_nodes_tree1
64 colors_tree2 = [0] * num_nodes_tree2
65
66 # Initialize counters for nodes of each color in both trees
67 color_counts_tree1 = [0, 0] # [count of color 0, count of color 1]
68 color_counts_tree2 = [0, 0] # [count of color 0, count of color 1]
69
70 # Color nodes in tree2 starting from node 0 as root
71 dfs_color_nodes(graph2, 0, -1, colors_tree2, 0, color_counts_tree2)
72
73 # Color nodes in tree1 starting from node 0 as root
74 dfs_color_nodes(graph1, 0, -1, colors_tree1, 0, color_counts_tree1)
75
76 # Get the maximum count between the two colors in tree2
77 max_color_count_tree2 = max(color_counts_tree2)
78
79 # For each node in tree1, calculate the result:
80 # max nodes from tree2 + nodes of same color in tree1
81 result = []
82 for node_idx in range(num_nodes_tree1):
83 node_color = colors_tree1[node_idx]
84 total = max_color_count_tree2 + color_counts_tree1[node_color]
85 result.append(total)
86
87 return result
88
1class Solution {
2 /**
3 * Finds the maximum number of target nodes for each node in the first tree
4 * by considering bipartite coloring of both trees.
5 *
6 * @param edges1 Edge list for the first tree
7 * @param edges2 Edge list for the second tree
8 * @return Array where ans[i] represents the maximum target nodes for node i
9 */
10 public int[] maxTargetNodes(int[][] edges1, int[][] edges2) {
11 // Build adjacency lists for both trees
12 List<Integer>[] graph1 = build(edges1);
13 List<Integer>[] graph2 = build(edges2);
14
15 int n = graph1.length; // Number of nodes in first tree
16 int m = graph2.length; // Number of nodes in second tree
17
18 // Arrays to store the color (0 or 1) of each node after bipartite coloring
19 int[] colors1 = new int[n];
20 int[] colors2 = new int[m];
21
22 // Count arrays to store how many nodes have color 0 or 1
23 int[] colorCount1 = new int[2]; // [count of color 0, count of color 1]
24 int[] colorCount2 = new int[2]; // [count of color 0, count of color 1]
25
26 // Perform DFS to color nodes in a bipartite manner (alternating 0 and 1)
27 dfs(graph2, 0, -1, colors2, 0, colorCount2);
28 dfs(graph1, 0, -1, colors1, 0, colorCount1);
29
30 // Get the maximum count between the two colors in the second tree
31 int maxColorCountInTree2 = Math.max(colorCount2[0], colorCount2[1]);
32
33 // Calculate result for each node in the first tree
34 int[] answer = new int[n];
35 for (int i = 0; i < n; i++) {
36 // For each node, add the max from tree2 and the count of same-colored nodes in tree1
37 answer[i] = maxColorCountInTree2 + colorCount1[colors1[i]];
38 }
39
40 return answer;
41 }
42
43 /**
44 * Builds an adjacency list representation of the tree from edge list.
45 *
46 * @param edges Array of edges where each edge is [nodeA, nodeB]
47 * @return Adjacency list representation of the tree
48 */
49 private List<Integer>[] build(int[][] edges) {
50 int numberOfNodes = edges.length + 1; // Tree with n nodes has n-1 edges
51
52 // Initialize adjacency list
53 List<Integer>[] graph = new List[numberOfNodes];
54 Arrays.setAll(graph, index -> new ArrayList<>());
55
56 // Add bidirectional edges to the adjacency list
57 for (int[] edge : edges) {
58 int nodeA = edge[0];
59 int nodeB = edge[1];
60 graph[nodeA].add(nodeB);
61 graph[nodeB].add(nodeA);
62 }
63
64 return graph;
65 }
66
67 /**
68 * Performs DFS traversal to color nodes in a bipartite manner.
69 *
70 * @param graph The adjacency list representation of the tree
71 * @param currentNode Current node being visited
72 * @param parent Parent of the current node (-1 if root)
73 * @param colors Array to store the color of each node
74 * @param currentColor Color to assign to the current node (0 or 1)
75 * @param colorCount Array to count nodes of each color
76 */
77 private void dfs(List<Integer>[] graph, int currentNode, int parent,
78 int[] colors, int currentColor, int[] colorCount) {
79 // Assign color to current node
80 colors[currentNode] = currentColor;
81 colorCount[currentColor]++;
82
83 // Visit all neighbors except the parent
84 for (int neighbor : graph[currentNode]) {
85 if (neighbor != parent) {
86 // Recursively color neighbor with opposite color (XOR with 1 toggles 0↔1)
87 dfs(graph, neighbor, currentNode, colors, currentColor ^ 1, colorCount);
88 }
89 }
90 }
91}
92
1class Solution {
2public:
3 vector<int> maxTargetNodes(vector<vector<int>>& edges1, vector<vector<int>>& edges2) {
4 // Build adjacency lists for both trees
5 auto graph1 = buildGraph(edges1);
6 auto graph2 = buildGraph(edges2);
7
8 int n = graph1.size(); // Number of nodes in tree1
9 int m = graph2.size(); // Number of nodes in tree2
10
11 // Store the color (0 or 1) for each node after bipartite coloring
12 vector<int> colors1(n, 0);
13 vector<int> colors2(m, 0);
14
15 // Count nodes of each color (0 or 1) in both trees
16 vector<int> colorCount1(2, 0); // colorCount1[0] = nodes with color 0, colorCount1[1] = nodes with color 1
17 vector<int> colorCount2(2, 0);
18
19 // Perform DFS to color nodes in a bipartite manner (alternating 0 and 1 by depth)
20 // Starting from node 0 with color 0
21 dfs(graph2, 0, -1, colors2, 0, colorCount2);
22 dfs(graph1, 0, -1, colors1, 0, colorCount1);
23
24 // Find the maximum count between the two color groups in tree2
25 int maxCountTree2 = max(colorCount2[0], colorCount2[1]);
26
27 // Calculate result for each node in tree1
28 // For each node i: answer = max color group from tree2 + same color group count from tree1
29 vector<int> result(n);
30 for (int i = 0; i < n; ++i) {
31 result[i] = maxCountTree2 + colorCount1[colors1[i]];
32 }
33
34 return result;
35 }
36
37private:
38 // Build adjacency list representation of the tree from edge list
39 vector<vector<int>> buildGraph(const vector<vector<int>>& edges) {
40 int numNodes = edges.size() + 1; // Tree with n nodes has n-1 edges
41 vector<vector<int>> adjacencyList(numNodes);
42
43 for (const auto& edge : edges) {
44 int nodeA = edge[0];
45 int nodeB = edge[1];
46 adjacencyList[nodeA].push_back(nodeB);
47 adjacencyList[nodeB].push_back(nodeA);
48 }
49
50 return adjacencyList;
51 }
52
53 // DFS to assign bipartite colors to nodes and count nodes of each color
54 // Parameters:
55 // graph: adjacency list of the tree
56 // currentNode: current node being visited
57 // parentNode: parent of current node (-1 if root)
58 // nodeColors: array to store color assignment for each node
59 // currentColor: color to assign to current node (0 or 1)
60 // colorCount: array to count nodes of each color
61 void dfs(const vector<vector<int>>& graph, int currentNode, int parentNode,
62 vector<int>& nodeColors, int currentColor, vector<int>& colorCount) {
63 // Assign color to current node
64 nodeColors[currentNode] = currentColor;
65 // Increment count for this color
66 colorCount[currentColor]++;
67
68 // Visit all neighbors except parent
69 for (int neighbor : graph[currentNode]) {
70 if (neighbor != parentNode) {
71 // Recursively color neighbor with opposite color (XOR with 1 flips 0↔1)
72 dfs(graph, neighbor, currentNode, nodeColors, currentColor ^ 1, colorCount);
73 }
74 }
75 }
76};
77
1/**
2 * Calculates maximum target nodes for each node in the first graph
3 * @param edges1 - Edge list for the first graph
4 * @param edges2 - Edge list for the second graph
5 * @returns Array where each element represents the maximum target nodes for corresponding node in graph1
6 */
7function maxTargetNodes(edges1: number[][], edges2: number[][]): number[] {
8 // Build adjacency lists for both graphs
9 const graph1: number[][] = build(edges1);
10 const graph2: number[][] = build(edges2);
11
12 // Get the number of nodes in each graph
13 const nodeCount1: number = graph1.length;
14 const nodeCount2: number = graph2.length;
15
16 // Arrays to store the color (0 or 1) of each node after bipartite coloring
17 const colors1: number[] = Array(nodeCount1).fill(0);
18 const colors2: number[] = Array(nodeCount2).fill(0);
19
20 // Count of nodes for each color [color0Count, color1Count]
21 const colorCounts1: number[] = [0, 0];
22 const colorCounts2: number[] = [0, 0];
23
24 // Perform DFS to color nodes and count nodes in each color group
25 // Starting from node 0 with initial color 0
26 dfs(graph2, 0, -1, colors2, 0, colorCounts2);
27 dfs(graph1, 0, -1, colors1, 0, colorCounts1);
28
29 // Get the maximum count from graph2's color groups
30 const maxColorCountGraph2: number = Math.max(...colorCounts2);
31
32 // Calculate result for each node in graph1
33 const result: number[] = Array(nodeCount1);
34 for (let i = 0; i < nodeCount1; i++) {
35 // For each node, add its color group count from graph1 and max from graph2
36 result[i] = maxColorCountGraph2 + colorCounts1[colors1[i]];
37 }
38
39 return result;
40}
41
42/**
43 * Builds an adjacency list representation of the graph from edge list
44 * @param edges - Array of edges where each edge is [nodeA, nodeB]
45 * @returns Adjacency list representation of the graph
46 */
47function build(edges: number[][]): number[][] {
48 // Number of nodes is edges count + 1 (for a tree)
49 const nodeCount: number = edges.length + 1;
50
51 // Initialize adjacency list
52 const adjacencyList: number[][] = Array.from({ length: nodeCount }, () => []);
53
54 // Add bidirectional edges to adjacency list
55 for (const [nodeA, nodeB] of edges) {
56 adjacencyList[nodeA].push(nodeB);
57 adjacencyList[nodeB].push(nodeA);
58 }
59
60 return adjacencyList;
61}
62
63/**
64 * Performs DFS to assign bipartite colors to nodes and count nodes in each color group
65 * @param graph - Adjacency list representation of the graph
66 * @param currentNode - Current node being visited
67 * @param parentNode - Parent node to avoid revisiting
68 * @param nodeColors - Array to store the color of each node
69 * @param currentColor - Color to assign to current node (0 or 1)
70 * @param colorCounts - Array to track count of nodes for each color
71 */
72function dfs(
73 graph: number[][],
74 currentNode: number,
75 parentNode: number,
76 nodeColors: number[],
77 currentColor: number,
78 colorCounts: number[]
79): void {
80 // Assign color to current node
81 nodeColors[currentNode] = currentColor;
82
83 // Increment count for this color
84 colorCounts[currentColor]++;
85
86 // Visit all neighbors except parent
87 for (const neighbor of graph[currentNode]) {
88 if (neighbor !== parentNode) {
89 // Recursively color neighbors with opposite color (XOR with 1 flips 0↔1)
90 dfs(graph, neighbor, currentNode, nodeColors, currentColor ^ 1, colorCounts);
91 }
92 }
93}
94
Time and Space Complexity
Time Complexity: O(n + m)
The algorithm consists of the following operations:
- Building graph
g1
fromedges1
: iterates throughn-1
edges, takingO(n)
time - Building graph
g2
fromedges2
: iterates throughm-1
edges, takingO(m)
time - DFS traversal on
g2
: visits each of them
nodes exactly once, takingO(m)
time - DFS traversal on
g1
: visits each of then
nodes exactly once, takingO(n)
time - Finding maximum of
cnt2
:O(1)
sincecnt2
has only 2 elements - Building the result list: iterates through
n
nodes withO(1)
work per node, takingO(n)
time
Total time complexity: O(n) + O(m) + O(m) + O(n) + O(1) + O(n) = O(n + m)
Space Complexity: O(n + m)
The algorithm uses the following space:
- Adjacency list
g1
: storesn
nodes with a total of2(n-1)
edges (each edge appears twice), takingO(n)
space - Adjacency list
g2
: storesm
nodes with a total of2(m-1)
edges, takingO(m)
space - Arrays
c1
andc2
:O(n)
andO(m)
space respectively - Arrays
cnt1
andcnt2
:O(1)
space each (fixed size of 2) - DFS recursion stack: at most
O(n)
forg1
andO(m)
forg2
in the worst case (when the tree is a chain) - Result array:
O(n)
space
Total space complexity: O(n) + O(m) + O(n) + O(m) + O(1) = O(n + m)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Tree Root Selection Assumption
The Pitfall: The most critical pitfall in this solution is assuming that we can arbitrarily choose node 0 as the root for both trees when performing DFS coloring. While this works for tree2 (since we're only interested in the maximum count of either color group), it can produce incorrect results for tree1 because the parity classification of nodes depends on which node is chosen as the reference point.
Why It Happens:
- When we color tree1 starting from node 0, we're essentially measuring distances from node 0
- But the problem asks for the maximum target nodes for each node
i
, meaning nodei
should be the reference point - The parity relationship between nodes changes depending on the reference node
Example Scenario:
Tree1: 0 -- 1 -- 2 -- 3
- If we color from node 0: colors = [0, 1, 0, 1]
- If we color from node 1: colors = [1, 0, 1, 0]
- If we color from node 2: colors = [0, 1, 0, 1]
When calculating target nodes for node 1, using node 0 as reference gives wrong parity relationships.
The Solution: We need to perform DFS coloring for each query node separately:
def maxTargetNodes(self, edges1: List[List[int]], edges2: List[List[int]]) -> List[int]:
def build_adjacency_list(edges):
num_nodes = len(edges) + 1
adjacency_list = [[] for _ in range(num_nodes)]
for node_a, node_b in edges:
adjacency_list[node_a].append(node_b)
adjacency_list[node_b].append(node_a)
return adjacency_list
def dfs_color_nodes(graph, current_node, parent_node, node_colors, current_color, color_counts):
node_colors[current_node] = current_color
color_counts[current_color] += 1
for neighbor in graph[current_node]:
if neighbor != parent_node:
dfs_color_nodes(graph, neighbor, current_node, node_colors, current_color ^ 1, color_counts)
graph1 = build_adjacency_list(edges1)
graph2 = build_adjacency_list(edges2)
num_nodes_tree1 = len(graph1)
num_nodes_tree2 = len(graph2)
# For tree2, we can use any root since we only need the max count
colors_tree2 = [0] * num_nodes_tree2
color_counts_tree2 = [0, 0]
dfs_color_nodes(graph2, 0, -1, colors_tree2, 0, color_counts_tree2)
max_color_count_tree2 = max(color_counts_tree2)
result = []
# For each node in tree1, perform DFS with that node as root
for query_node in range(num_nodes_tree1):
colors_tree1 = [0] * num_nodes_tree1
color_counts_tree1 = [0, 0]
# Color tree1 with query_node as the root
dfs_color_nodes(graph1, query_node, -1, colors_tree1, 0, color_counts_tree1)
# Nodes with color 0 (same color as root) are at even distance
# These are the target nodes from tree1
target_nodes_tree1 = color_counts_tree1[0]
# Total = target nodes from tree1 + max possible from tree2
result.append(target_nodes_tree1 + max_color_count_tree2)
return result
2. Optimization Consideration
While the corrected solution above works, it has O(n²) time complexity because we run DFS for each node. If this is too slow, a more sophisticated approach would involve:
- Pre-computing parity relationships between all pairs of nodes
- Or using the fact that we can derive parity relationships transitively
However, for moderate-sized trees (n ≤ 1000), the O(n²) solution should be acceptable.
3. Edge Case: Single Node Trees
Another pitfall is not handling the edge case where one or both trees have only a single node (empty edges list). The solution should handle this gracefully since a single node is always target to itself.
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!