Facebook Pixel

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 length n-1 representing edges in the first tree, where edges1[i] = [ai, bi] means there's an edge between nodes ai and bi
  • edges2 of length m-1 representing edges in the second tree, where edges2[i] = [ui, vi] means there's an edge between nodes ui and vi

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 and edges2.

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 and n-1 edges.

DFS

  • Yes: We arrive at DFS as the recommended approach for tree problems.

Why DFS is appropriate for this problem:

  1. Tree Traversal: We need to traverse both trees to understand their structure and calculate distances between nodes.

  2. 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.

  3. 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.

  4. 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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. 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)
  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 as i 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 tree
  • a: current node
  • fa: parent node (to avoid cycles)
  • c: array storing parity value for each node
  • d: 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 1
  • t 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 Evaluator

Example 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 from edges1: iterates through n-1 edges, taking O(n) time
  • Building graph g2 from edges2: iterates through m-1 edges, taking O(m) time
  • DFS traversal on g2: visits each of the m nodes exactly once, taking O(m) time
  • DFS traversal on g1: visits each of the n nodes exactly once, taking O(n) time
  • Finding maximum of cnt2: O(1) since cnt2 has only 2 elements
  • Building the result list: iterates through n nodes with O(1) work per node, taking O(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: stores n nodes with a total of 2(n-1) edges (each edge appears twice), taking O(n) space
  • Adjacency list g2: stores m nodes with a total of 2(m-1) edges, taking O(m) space
  • Arrays c1 and c2: O(n) and O(m) space respectively
  • Arrays cnt1 and cnt2: O(1) space each (fixed size of 2)
  • DFS recursion stack: at most O(n) for g1 and O(m) for g2 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 node i 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More