3203. Find Minimum Diameter After Merging Two Trees
Problem Description
There exist two undirected trees with n
and m
nodes, numbered from 0
to n - 1
and from 0
to m - 1
, respectively. You are given two 2D integer arrays edges1
and edges2
of lengths n - 1
and m - 1
, respectively, where edges1[i] = [a_i, b_i]
indicates that there is an edge between nodes a_i
and b_i
in the first tree and edges2[i] = [u_i, v_i]
indicates that there is an edge between nodes u_i
and v_i
in the second tree.
You must connect one node from the first tree with another node from the second tree with an edge.
Return the minimum possible diameter of the resulting tree.
The diameter of a tree is the length of the longest path between any two nodes in the tree.
Intuition
To solve this problem, we need to understand how adding an edge between two trees affects their diameter. The key is to calculate the diameters of both trees individually and see how merging them could potentially reduce or keep the same diameter.
-
Initial Diameters: Determine the diameter of each tree individually. The diameter of a tree is the longest path between any two nodes. We can efficiently find this using a two-pass Depth First Search (DFS):
- Perform a DFS from any node to find the farthest node
a
. - From
a
, perform another DFS to find the farthest nodeb
. The distance betweena
andb
is the diameter of the tree.
- Perform a DFS from any node to find the farthest node
-
Edge Addition Impact: When we add an edge between two trees, we essentially connect the two trees' longest paths. The new diameter could either be one of the original diameters (if it's larger than the combination path) or be determined by the longest possible path that uses nodes from both trees.
-
Calculate New Diameters:
- Calculate
r1
as the effective "radius" of the first tree:(d1 + 1) // 2
. - Calculate
r2
as the effective "radius" of the second tree:(d2 + 1) // 2
. - The maximum diameter after merging can be
max(d1, d2, r1 + r2 + 1)
. This accounts for the longest possible path that stretches across both trees plus the edge that connects them.
- Calculate
By adopting this approach, we can efficiently determine the minimum possible diameter after merging the two trees with a single edge.
Learn more about Tree, Depth-First Search, Breadth-First Search and Graph patterns.
Solution Approach
To implement the solution and find the minimum possible diameter after merging two trees, we employ the following steps and techniques:
- Tree Diameter Calculation:
- We need to calculate the diameter for both trees separately. This is achieved through a method
treeDiameter
, which uses a Depth-First Search (DFS) strategy. - Start with an arbitrary node and perform a DFS from that node to find the farthest node, say
a
. - Conduct another DFS starting from
a
to find the farthest node from it, which we callb
. The distance betweena
andb
represents the tree's diameter.
- We need to calculate the diameter for both trees separately. This is achieved through a method
def treeDiameter(self, edges: List[List[int]]) -> int:
def dfs(i: int, fa: int, t: int):
for j in g[i]:
if j != fa:
dfs(j, i, t + 1)
nonlocal ans, a
if ans < t:
ans = t
a = i
g = defaultdict(list)
for a, b in edges:
g[a].append(b)
g[b].append(a)
ans = a = 0
dfs(0, -1, 0)
dfs(a, -1, 0)
return ans
- Determine New Diameter after Merging:
- Calculate
d1
andd2
as the diameters of the first and second tree respectively. - Compute
r1
andr2
as half the diameters, effectively representing the radius, rounded up:r1 = (d1 + 1) // 2
andr2 = (d2 + 1) // 2
. - The potential new diameter after merging is calculated using
max(d1, d2, r1 + r2 + 1)
. This checks:- If the existing diameters
d1
ord2
are greater than the combined diameter via path stretching across the new connection (r1 + r2 + 1
).
- If the existing diameters
- Calculate
def minimumDiameterAfterMerge(self, edges1: List[List[int]], edges2: List[List[int]]) -> int:
d1 = self.treeDiameter(edges1)
d2 = self.treeDiameter(edges2)
return max(d1, d2, (d1 + 1) // 2 + (d2 + 1) // 2 + 1)
By following these steps, the solution efficiently determines the minimum possible diameter after merging two trees by considering both individual diameters as well as the new longest path that can be created by connecting them.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through an example to illustrate the solution approach.
Suppose we have two trees, the first tree with 4 nodes and the second tree with 3 nodes. The edges for these trees are given as follows:
- Tree 1:
edges1 = [[0, 1], [1, 2], [1, 3]]
- Tree 2:
edges2 = [[0, 1], [1, 2]]
Step 1: Calculate the Diameters of Both Trees Individually
-
Tree 1 Diameter Calculation:
- Start DFS from node
0
. Traverse to find the farthest node, which becomes2
or3
with a distance of2
. - From node
2
, perform another DFS to find the farthest node, which is3
with a distance of2
. Thus, the diameterd1 = 2
.
- Start DFS from node
-
Tree 2 Diameter Calculation:
- Start DFS from node
0
. Traverse to find the farthest node, which is2
with a distance of2
. - From node
2
, perform another DFS to find the farthest node, which is0
with a distance of2
. Hence, the diameterd2 = 2
.
- Start DFS from node
Step 2: Calculate Radii of Both Trees
- For Tree 1, calculate the radius:
r1 = (d1 + 1) // 2 = (2 + 1) // 2 = 1
- For Tree 2, calculate the radius:
r2 = (d2 + 1) // 2 = (2 + 1) // 2 = 1
Step 3: Determine the Minimum Diameter After Merging
- Compute the potential new diameter after merging:
max(d1, d2, r1 + r2 + 1) = max(2, 2, 1 + 1 + 1) = 3
The minimum possible diameter of the resulting tree after connecting any node from Tree 1 to any node from Tree 2 is 3
.
By following these steps, the approach demonstrates how to efficiently find the minimum possible diameter after merging two trees with one connecting edge.
Solution Implementation
1from typing import List
2from collections import defaultdict
3
4class Solution:
5 def minimumDiameterAfterMerge(
6 self, edges1: List[List[int]], edges2: List[List[int]]
7 ) -> int:
8 # Calculate the diameter of each tree
9 diameter1 = self.treeDiameter(edges1)
10 diameter2 = self.treeDiameter(edges2)
11
12 # The minimum diameter after merging is the maximum of the two diameters
13 # or the sum of half of each diameter plus one.
14 return max(diameter1, diameter2, (diameter1 + 1) // 2 + (diameter2 + 1) // 2 + 1)
15
16 def treeDiameter(self, edges: List[List[int]]) -> int:
17 # Depth-first search to find the farthest node from the starting node.
18 def dfs(node: int, parent: int, depth: int):
19 for neighbor in graph[node]:
20 if neighbor != parent:
21 dfs(neighbor, node, depth + 1)
22
23 nonlocal max_depth, farthest_node
24 if max_depth < depth:
25 max_depth = depth
26 farthest_node = node
27
28 # Build the adjacency list representation of the graph.
29 graph = defaultdict(list)
30 for node1, node2 in edges:
31 graph[node1].append(node2)
32 graph[node2].append(node1)
33
34 # Initialize variables for the maximum depth and farthest node.
35 max_depth = farthest_node = 0
36 # First DFS to find one endpoint of the diameter
37 dfs(0, -1, 0)
38 # Second DFS from the farthest node found to determine the diameter
39 dfs(farthest_node, -1, 0)
40
41 return max_depth
42
1import java.util.ArrayList;
2import java.util.Arrays;
3import java.util.List;
4
5class Solution {
6 // Graph adjacency list
7 private List<Integer>[] graph;
8 // Maximum diameter found
9 private int maxDiameter;
10 // Furthest node found
11 private int furthestNode;
12
13 /**
14 * Calculates the minimum diameter after merging two trees.
15 *
16 * @param edges1 The edges of the first tree.
17 * @param edges2 The edges of the second tree.
18 * @return The minimum diameter after merging the trees.
19 */
20 public int minimumDiameterAfterMerge(int[][] edges1, int[][] edges2) {
21 int diameter1 = treeDiameter(edges1);
22 int diameter2 = treeDiameter(edges2);
23
24 // Calculate the effective diameter after potential merging
25 int mergedDiameter = (diameter1 + 1) / 2 + (diameter2 + 1) / 2 + 1;
26
27 // Return the maximum diameter
28 return Math.max(Math.max(diameter1, diameter2), mergedDiameter);
29 }
30
31 /**
32 * Finds the diameter of a tree.
33 *
34 * @param edges The edges representing the tree.
35 * @return The diameter of the tree.
36 */
37 public int treeDiameter(int[][] edges) {
38 int n = edges.length + 1; // Number of nodes
39
40 // Initialize graph as an adjacency list
41 graph = new List[n];
42 Arrays.setAll(graph, k -> new ArrayList<>());
43
44 // Reset maximum diameter and furthest node
45 maxDiameter = 0;
46 furthestNode = 0;
47
48 // Construct the graph
49 for (int[] edge : edges) {
50 int node1 = edge[0];
51 int node2 = edge[1];
52 graph[node1].add(node2);
53 graph[node2].add(node1);
54 }
55
56 // Perform DFS to find the furthest node from an arbitrary start (here, node 0)
57 dfs(0, -1, 0);
58 // Perform DFS again from the furthest node found to determine the tree's diameter
59 dfs(furthestNode, -1, 0);
60
61 // Return the maximum diameter found
62 return maxDiameter;
63 }
64
65 /**
66 * Depth-First Search to compute the furthest distance from the current node.
67 *
68 * @param currentNode The current node in DFS.
69 * @param parent The parent node in DFS.
70 * @param currentLength The distance from the starting node.
71 */
72 private void dfs(int currentNode, int parent, int currentLength) {
73 for (int neighbor : graph[currentNode]) {
74 if (neighbor != parent) {
75 dfs(neighbor, currentNode, currentLength + 1);
76 }
77 }
78
79 // Update the maximum distance and furthest node if a longer path is found
80 if (maxDiameter < currentLength) {
81 maxDiameter = currentLength;
82 furthestNode = currentNode;
83 }
84 }
85}
86
1#include <vector>
2#include <algorithm>
3using namespace std;
4
5class Solution {
6public:
7 int minimumDiameterAfterMerge(vector<vector<int>>& edges1, vector<vector<int>>& edges2) {
8 // Calculate the diameter of each tree
9 int d1 = treeDiameter(edges1);
10 int d2 = treeDiameter(edges2);
11
12 // Determine the minimum possible diameter after merging the two trees
13 // The diameter of the merged tree is the maximum of the diameters of the two original trees,
14 // or the sum of half the diameters of each tree (rounded up) plus 1.
15 return max({d1, d2, (d1 + 1) / 2 + (d2 + 1) / 2 + 1});
16 }
17
18 int treeDiameter(vector<vector<int>>& edges) {
19 int n = edges.size() + 1; // Number of nodes
20
21 // Create adjacency list for the tree
22 vector<vector<int>> graph(n);
23 for (const auto& e : edges) {
24 int a = e[0], b = e[1];
25 graph[a].push_back(b);
26 graph[b].push_back(a);
27 }
28
29 int diameter = 0, farthestNode = 0;
30
31 // Depth-First Search (DFS) to calculate the diameter
32 // Lambda function used for DFS traversal
33 auto dfs = [&](auto&& dfs, int node, int parent, int depth) -> void {
34 for (int neighbor : graph[node]) {
35 if (neighbor != parent) {
36 dfs(dfs, neighbor, node, depth + 1);
37 }
38 }
39 // Update farthest distance and node
40 if (diameter < depth) {
41 diameter = depth;
42 farthestNode = node;
43 }
44 };
45
46 // Execute DFS twice to find the tree diameter
47 dfs(dfs, 0, -1, 0); // First DFS to find the farthest node from root
48 dfs(dfs, farthestNode, -1, 0); // Second DFS from farthest node to find the actual diameter
49
50 return diameter;
51 }
52};
53
1function minimumDiameterAfterMerge(edges1: number[][], edges2: number[][]): number {
2 // Calculate the diameter of the first tree.
3 const d1 = treeDiameter(edges1);
4 // Calculate the diameter of the second tree.
5 const d2 = treeDiameter(edges2);
6
7 // Return the maximum of the two diameters or the minimum possible diameter after merging.
8 return Math.max(d1, d2, Math.ceil(d1 / 2) + Math.ceil(d2 / 2) + 1);
9}
10
11function treeDiameter(edges: number[][]): number {
12 // Number of nodes is one more than the number of edges in a tree.
13 const n = edges.length + 1;
14
15 // Initialize adjacency list for the graph.
16 const graph: number[][] = Array.from({ length: n }, () => []);
17
18 // Construct the graph.
19 for (const [a, b] of edges) {
20 graph[a].push(b);
21 graph[b].push(a);
22 }
23
24 // Variables to store the maximum distance found and the corresponding node.
25 let [maxDistance, farthestNode] = [0, 0];
26
27 // Depth-first search helper function.
28 const dfs = (currentNode: number, parent: number, distance: number): void => {
29 // Traverse each connected node.
30 for (const neighbor of graph[currentNode]) {
31 // Continue traversal only if the node is not the parent.
32 if (neighbor !== parent) {
33 dfs(neighbor, currentNode, distance + 1);
34 }
35 }
36 // Update the maximum distance and farthest node if current distance is greater.
37 if (maxDistance < distance) {
38 maxDistance = distance;
39 farthestNode = currentNode;
40 }
41 };
42
43 // Perform a DFS to find a farthest node from node 0.
44 dfs(0, -1, 0);
45 // Perform another DFS from the farthest node found to determine the tree's diameter.
46 dfs(farthestNode, -1, 0);
47 return maxDistance;
48}
49
Time and Space Complexity
The minimumDiameterAfterMerge
function processes two separate tree structures represented by edges1
and edges2
. For each tree, it calculates the diameter using the treeDiameter
function.
-
Time Complexity: Each call to
treeDiameter
involves two depth-first search (DFS) passes. Constructing the adjacency list takesO(n)
time for the edges, and each DFS pass also takesO(n)
, wheren
is the number of nodes in a tree. SincetreeDiameter
is invoked twice, the overall time complexity forminimumDiameterAfterMerge
isO(n + m)
wheren
andm
are the number of edges inedges1
andedges2
, respectively. -
Space Complexity: The space required is mainly due to the adjacency list and the recursion stack. The adjacency list requires
O(n)
space for storing edges, and the recursion stack in DFS can also take up toO(n)
space for each tree. Therefore, the total space complexity isO(n + m)
, accounting for the two trees.
Learn more about how to find time and space complexity quickly.
Which of these properties could exist for a graph but not a tree?
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 algomonster s3 us east 2 amazonaws com 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
https algomonster s3 us east 2 amazonaws com 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
Want a Structured Path to Master System Design Too? Don’t Miss This!