LeetCode 3372 - Maximize the Number of Target Nodes After Connecting Trees I
We are given two separate undirected trees. The first tree contains n nodes labeled from 0 to n - 1, and the second tree contains m nodes labeled from 0 to m - 1. A node u is considered a target node for another node v if the shortest path distance between them is at most k.
Difficulty: 🟡 Medium
Topics: Tree, Depth-First Search, Breadth-First Search
Solution
LeetCode 3372 - Maximize the Number of Target Nodes After Connecting Trees I
Problem Understanding
We are given two separate undirected trees.
The first tree contains n nodes labeled from 0 to n - 1, and the second tree contains m nodes labeled from 0 to m - 1.
A node u is considered a target node for another node v if the shortest path distance between them is at most k.
For every node i in the first tree, we are allowed to add exactly one temporary edge connecting:
Problem Understanding
This problem gives us two separate undirected trees. The first tree has n nodes labeled from 0 to n - 1, and the second tree has m nodes labeled from 0 to m - 1.
For any two nodes, a node u is considered a target node of v if the shortest path distance between them is less than or equal to k. Since these are trees, there is exactly one simple path between any two nodes.
For every node i in the first tree, we are allowed to add exactly one edge connecting:
- one node from the first tree
- one node from the second tree
After adding this edge, we want to maximize how many nodes become target nodes of i.
Each query is independent. After computing the answer for one node i, the added edge is removed before processing the next node.
The important detail is that the new edge can connect any node from tree 1 to any node from tree 2. We are free to choose the best connection separately for every node i.
The output is an array answer where:
answer[i]equals the maximum number of nodes within distancekfrom nodeiafter optimally connecting the two trees.
Since both inputs are trees:
- there are no cycles
- there is exactly one simple path between every pair of nodes
The constraints are:
n, m <= 1000k <= 1000
This is small enough that running BFS or DFS from every node is completely feasible.
Some important edge cases immediately stand out:
k = 0, where only the node itself can be counted- very small trees, such as trees with only 2 nodes
- highly unbalanced trees, like chains
- star-shaped trees where one node connects to all others
- cases where the second tree contributes nothing because traversing the bridge already consumes all allowed distance
The problem guarantees both graphs are valid trees, so we never need to handle disconnected components or invalid inputs.
After adding this edge, we want to maximize the number of nodes that are within distance k from node i.
The important detail is that every query is independent. After computing the best answer for node i, the added edge is removed before processing the next node.
The result is an array where:
answer[i]represents the maximum number of target nodes reachable from nodei- the count includes nodes from both trees after adding one connecting edge
The constraints are relatively small:
n, m <= 1000- both graphs are guaranteed to be valid trees
k <= 1000
Because the graphs are trees and the total number of nodes is only around 2000, running BFS or DFS from every node is feasible.
There are several important observations and edge cases:
When k = 0, a node can only reach itself. Since crossing into the second tree requires using the newly added edge, no node in the second tree can be reached because that would require distance at least 1. Therefore every answer becomes 1.
Another important observation is that the newly added edge always consumes one distance unit. If we connect node a in the first tree to node b in the second tree, then any node in the second tree reachable from i must satisfy:
$$\text{dist}(i, a) + 1 + \text{dist}(b, x) \le k$$
This structure is the key to the optimized solution.
Because the input graphs are guaranteed to be trees:
- there are no cycles
- there is exactly one path between nodes
- BFS distance computation is straightforward and efficient
Approaches
Brute Force Approach
The brute force solution tries every possible way to connect the two trees.
For every node i in the first tree:
- Try every node
uin the first tree as the bridge endpoint. - Try every node
vin the second tree as the bridge endpoint. - Add the temporary edge
(u, v). - Run BFS from
iin the combined graph. - Count how many nodes are within distance
k. - Keep the maximum result.
This works because it explicitly evaluates every legal connection and directly computes the resulting reachable nodes. The brute force approach tries every possible connection between the two trees for every node in the first tree.
For a fixed node i in the first tree:
- Try connecting every node
ain the first tree with every nodebin the second tree. - Build the temporary merged graph.
- Run BFS from node
i. - Count how many nodes are within distance
k. - Keep the maximum count.
This approach is correct because it explicitly evaluates every valid way to connect the trees and computes the exact number of reachable nodes.
However, it is far too slow.
There are:
n * mpossible bridge choices per querynqueries- each BFS costs
O(n + m)
The total complexity becomes roughly:
O(n * n * m * (n + m))
With n, m <= 1000, this becomes impractical.
Key Insight
The crucial observation is that the two trees contribute independently.
Suppose we want to compute the answer for node i.
If we connect:
- node
uin tree 1 - node
vin tree 2
then any node x in tree 2 is reachable from i with distance:
dist(i, u) + 1 + dist(v, x)
The bridge edge itself costs 1.
To maximize reachable nodes in tree 2, we should:
- spend as little distance as possible before entering tree 2
The best choice is always:
u = i
because then:
dist(i, u) = 0
Now the condition for nodes in tree 2 becomes:
1 + dist(v, x) <= k
which simplifies to:
dist(v, x) <= k - 1
Therefore:
- for tree 1, we only need the count of nodes within distance
kfrom every node - for tree 2, we only need the maximum count of nodes within distance
k - 1from any node
These can both be precomputed using BFS from every node.
n * mpossible edges to add- each BFS costs
O(n + m) - and we repeat this for every node in the first tree
The total complexity becomes approximately:
$$O(n \times n \times m \times (n+m))$$
With n, m <= 1000, this becomes impractical.
Optimal Approach
The key insight is that the two trees can be analyzed independently.
Suppose we want the answer for node i in the first tree.
If we connect:
- node
ain tree 1 - node
bin tree 2
then any node x in tree 2 is reachable from i only if:
$$\text{dist}(i, a) + 1 + \text{dist}(b, x) \le k$$
To maximize reachable nodes in tree 2, we should minimize the cost spent inside tree 1.
The best choice is always:
$$a = i$$
because then:
$$\text{dist}(i, a) = 0$$
So we spend only one distance unit crossing the new edge.
That means:
- nodes reachable inside tree 1 are simply all nodes within distance
kfromi - nodes reachable inside tree 2 are all nodes within distance
k - 1from the chosen connection nodeb
Therefore:
$$\text{answer}[i] = (\text{nodes within distance } k \text{ from } i \text{ in tree1}) + (\max \text{ nodes within distance } k-1 \text{ from any node in tree2})$$
This completely decouples the problem.
We can:
- Precompute reachable counts for every node in tree 1 using BFS.
- Precompute reachable counts for every node in tree 2 using BFS.
- Find the maximum reachable count in tree 2 for distance
k - 1. - Add that value to every node's count in tree 1.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n² × m × (n + m)) | O(n + m) | Tries every possible bridge and recomputes BFS each time |
| Optimal | O(n² + m²) | O(n + m) | Precompute reachable counts with BFS from every node |
| Brute Force | O(n²m(n+m)) | O(n+m) | Tries every possible connection and runs BFS each time |
| Optimal | O(n² + m²) | O(n+m) | Precomputes reachability counts independently for both trees |
Algorithm Walkthrough
- Build adjacency lists for both trees.
Since the graphs are trees and undirected, adjacency lists are the most efficient representation. They allow efficient BFS traversal from any node. 2. Create a helper BFS function.
The BFS function takes:
- an adjacency list
- a starting node
- a maximum allowed distance
It returns how many nodes are reachable within that distance. 3. Compute reachable counts for tree 1.
For every node i in tree 1:
- run BFS with limit
k - store how many nodes are within distance
k
This value represents how many nodes inside tree 1 are always target nodes for i, regardless of where we connect the second tree.
4. Compute the best possible contribution from tree 2.
Any node reached in tree 2 must spend one edge crossing the bridge.
Therefore, nodes in tree 2 must satisfy:
distance <= k - 1
For every node in tree 2:
- run BFS with limit
k - 1 - compute how many nodes are reachable
- keep the maximum value
- Combine the results.
For every node i in tree 1:
answer[i] = reachable_in_tree1[i] + best_tree2
- Return the final array.
Why it works
The key property is that crossing between trees always costs exactly one edge.
For any node i, the optimal bridge must connect directly from i into tree 2. Any other node in tree 1 would waste additional distance budget before even entering the second tree.
Once we connect directly from i to some node v in tree 2, the number of reachable nodes in tree 2 depends only on how many nodes are within distance k - 1 from v.
Thus the problem cleanly separates into:
- local reachability in tree 1
- best possible reachability in tree 2
This guarantees correctness. Since the input is given as edge lists, adjacency lists allow efficient BFS traversal. Trees are sparse graphs, so adjacency lists are the natural representation. 2. Create a helper BFS function.
The helper function takes:
- a graph
- a starting node
- a maximum allowed distance
It performs BFS and counts how many nodes are reachable within that distance. 3. Compute reachable counts for every node in tree 1.
For each node i:
- run BFS with limit
k - count all nodes within distance
k - store the result in
count1[i]
- Compute reachable counts for every node in tree 2.
Since crossing the connecting edge costs one step, only k - 1 distance remains for traversal inside tree 2.
For each node j in tree 2:
- run BFS with limit
k - 1 - count reachable nodes
- track the maximum value across all nodes
- Build the final answer.
For every node i in tree 1:
$$\text{answer}[i] = \text{count1}[i] + \text{bestTree2}$$
where bestTree2 is the maximum reachable count found in tree 2.
Why it works
The critical property is that the added edge contributes exactly one distance unit.
For node i, any path into tree 2 must look like:
$$i \rightarrow a \rightarrow b \rightarrow x$$
where:
ais the connection node in tree 1bis the connection node in tree 2
The total distance becomes:
$$\text{dist}(i,a) + 1 + \text{dist}(b,x)$$
To maximize remaining distance budget for tree 2, we minimize dist(i,a). The minimum possible value is 0, achieved by choosing a = i.
Therefore the optimal strategy always connects node i directly to some node in tree 2.
Once that observation is made, the two trees become independent subproblems.
Python Solution
from collections import deque
from typing import List
class Solution:
def maxTargetNodes(
self,
edges1: List[List[int]],
edges2: List[List[int]],
k: int
) -> List[int]:
n = len(edges1) + 1
m = len(edges2) + 1
graph1 = [[] for _ in range(n)]
graph2 = [[] for _ in range(m)]
for u, v in edges1:
graph1[u].append(v)
graph1[v].append(u)
for u, v in edges2:
graph2[u].append(v)
graph2[v].append(u)
def build_graph(size: int, edges: List[List[int]]) -> List[List[int]]:
graph = [[] for _ in range(size)]
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
return graph
graph1 = build_graph(n, edges1)
graph2 = build_graph(m, edges2)
def bfs_count(graph: List[List[int]], start: int, limit: int) -> int:
if limit < 0:
return 0
visited = [False] * len(graph)
queue = deque([(start, 0)])
visited[start] = True
count = 0
while queue:
node, dist = queue.popleft()
if dist > limit:
continue
count += 1
for neighbor in graph[node]:
if not visited[neighbor]:
visited[neighbor] = True
queue.append((neighbor, dist + 1))
return count
tree1_counts = [0] * n
for i in range(n):
tree1_counts[i] = bfs_count(graph1, i, k)
best_tree2 = 0
for i in range(m):
best_tree2 = max(
best_tree2,
bfs_count(graph2, i, k - 1)
for node in range(n):
tree1_counts[node] = bfs_count(graph1, node, k)
best_tree2 = 0
for node in range(m):
best_tree2 = max(
best_tree2,
bfs_count(graph2, node, k - 1)
)
answer = []
for count in tree1_counts:
answer.append(count + best_tree2)
return answer
The implementation starts by constructing adjacency lists for both trees. Since the input graphs are undirected, every edge is added in both directions.
The bfs_count helper performs a standard breadth-first search while tracking distances. It counts how many nodes are reachable within the allowed limit.
The special case limit < 0 is important because when k = 0, entering tree 2 is impossible. In that situation, tree 2 contributes zero nodes.
Next, the algorithm computes:
- reachable counts within distance
kfor every node in tree 1 - the maximum reachable count within distance
k - 1for tree 2
Finally, each result is formed by combining these two values. The implementation begins by converting both edge lists into adjacency lists. Since trees are undirected, every edge is inserted in both directions.
The bfs_count helper function computes how many nodes are reachable within a distance limit. BFS is ideal because it naturally explores nodes in increasing distance order.
The function keeps:
- a queue storing
(node, distance) - a visited array to avoid revisiting nodes
- a running count of valid nodes
For tree 1, BFS is run from every node using distance limit k.
For tree 2, BFS is run from every node using distance limit k - 1, because one edge is already consumed when crossing between trees.
The largest reachable count in tree 2 is stored in best_tree2.
Finally, each answer becomes:
$$\text{reachable in tree1} + \text{best reachable in tree2}$$
Go Solution
package main
import "container/list"
func maxTargetNodes(edges1 [][]int, edges2 [][]int, k int) []int {
n := len(edges1) + 1
m := len(edges2) + 1
graph1 := make([][]int, n)
graph2 := make([][]int, m)
for _, edge := range edges1 {
u, v := edge[0], edge[1]
graph1[u] = append(graph1[u], v)
graph1[v] = append(graph1[v], u)
}
for _, edge := range edges2 {
u, v := edge[0], edge[1]
graph2[u] = append(graph2[u], v)
graph2[v] = append(graph2[v], u)
}
buildGraph := func(size int, edges [][]int) [][]int {
graph := make([][]int, size)
for _, edge := range edges {
u, v := edge[0], edge[1]
graph[u] = append(graph[u], v)
graph[v] = append(graph[v], u)
}
return graph
}
graph1 := buildGraph(n, edges1)
graph2 := buildGraph(m, edges2)
bfsCount := func(graph [][]int, start int, limit int) int {
if limit < 0 {
return 0
}
visited := make([]bool, len(graph))
type Pair struct {
type State struct {
node int
dist int
}
queue := []Pair{{start, 0}}
visited[start] = true
count := 0
head := 0
for head < len(queue) {
current := queue[head]
head++
node := current.node
dist := current.dist
if dist > limit {
queue := list.New()
queue.PushBack(State{start, 0})
visited[start] = true
count := 0
for queue.Len() > 0 {
front := queue.Front()
queue.Remove(front)
current := front.Value.(State)
if current.dist > limit {
continue
}
count++
for _, neighbor := range graph[node] {
if !visited[neighbor] {
visited[neighbor] = true
queue = append(queue, Pair{neighbor, dist + 1})
for _, neighbor := range graph[current.node] {
if !visited[neighbor] {
visited[neighbor] = true
queue.PushBack(State{
node: neighbor,
dist: current.dist + 1,
})
}
}
}
return count
}
tree1Counts := make([]int, n)
for i := 0; i < n; i++ {
tree1Counts[i] = bfsCount(graph1, i, k)
}
bestTree2 := 0
for i := 0; i < m; i++ {
count := bfsCount(graph2, i, k-1)
if count > bestTree2 {
bestTree2 = count
value := bfsCount(graph2, i, k-1)
if value > bestTree2 {
bestTree2 = value
}
}
answer := make([]int, n)
for i := 0; i < n; i++ {
answer[i] = tree1Counts[i] + bestTree2
}
return answer
}
The Go version follows exactly the same algorithmic structure as the Python implementation.
A custom Pair struct stores (node, distance) values inside the BFS queue. Instead of using a deque, Go uses a slice with a moving head index for efficient queue operations.
Since all distances and counts are well within standard integer limits, regular int values are sufficient.
The Go implementation follows the same logic as the Python version.
The main difference is that Go does not have a built in deque, so container/list is used to implement BFS queues.
Go slices naturally represent adjacency lists efficiently. Since all values fit comfortably within standard integer ranges, no overflow handling is required.
Unlike Python, Go requires explicit struct definitions for queue states, so a State struct stores (node, dist) pairs during BFS traversal.
Worked Examples
Example 1
edges1 = [[0,1],[0,2],[2,3],[2,4]]
edges2 = [[0,1],[0,2],[0,3],[2,7],[1,4],[4,5],[4,6]]
k = 2
Step 1: Compute Reachability in Tree 1
Tree 1 structure:
1
|
0
|
2
/ \
3 4
We run BFS from every node with distance limit 2.
| Start Node | Reachable Nodes | Count |
|---|---|---|
| 0 | 0,1,2,3,4 | 5 |
| 1 | 1,0,2 | 3 |
| 2 | 2,0,3,4,1 | 5 |
| 3 | 3,2,0,4 | 4 |
| 4 | 4,2,0,3 | 4 |
So:
tree1_counts = [5,3,5,4,4]
Step 2: Compute Best Reachability in Tree 2
Since crossing the bridge costs one edge:
remaining distance = k - 1 = 1
We compute nodes reachable within distance 1.
| Start Node | Reachable Count |
|---|---|
| 0 | 4 |
| 1 | 3 |
| 2 | 3 |
| 3 | 2 |
| 4 | 4 |
| 5 | 2 |
| 6 | 2 |
| 7 | 2 |
Best contribution:
best_tree2 = 4
Step 3: Build Final Answer
| Node | Tree 1 Count | Best Tree 2 | Final |
|---|---|---|---|
| 0 | 5 | 4 | 9 |
| 1 | 3 | 4 | 7 |
| 2 | 5 | 4 | 9 |
| 3 | 4 | 4 | 8 |
| 4 | 4 | 4 | 8 |
Final answer:
[9,7,9,8,8]
Example 2
edges1 = [[0,1],[0,2],[0,3],[0,4]]
edges2 = [[0,1],[1,2],[2,3]]
k = 1
Tree 1 Reachability
Distance limit is 1.
| Start Node | Reachable Count |
|---|---|
| 0 | 5 |
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
| 4 | 2 |
Tree 2 Reachability
Remaining distance:
k - 1 = 0
Only the connected node itself is reachable.
| Start Node | Reachable Count |
|---|---|
| 0 | 1 |
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
So:
best_tree2 = 1
Final Computation
| Node | Tree 1 | Tree 2 | Final |
|---|---|---|---|
| 0 | 5 | 1 | 6 |
| 1 | 2 | 1 | 3 |
| 2 | 2 | 1 | 3 |
| 3 | 2 | 1 | 3 |
| 4 | 2 | 1 | 3 |
Final answer:
[6,3,3,3,3]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n² + m²) | BFS is run from every node in both trees |
| Space | O(n + m) | Adjacency lists, visited arrays, and BFS queue |
Each BFS on a tree visits every node once, giving O(V) complexity per traversal.
Since we run BFS:
ntimes on the first treemtimes on the second tree
the total complexity becomes:
O(n² + m²)
With n, m <= 1000, this is efficient enough.
Test Cases
from typing import List
class Solution:
def maxTargetNodes(self, edges1: List[List[int]], edges2: List[List[int]], k: int) -> List[int]:
from collections import deque
n = len(edges1) + 1
m = len(edges2) + 1
graph1 = [[] for _ in range(n)]
graph2 = [[] for _ in range(m)]
for u, v in edges1:
graph1[u].append(v)
graph1[v].append(u)
for u, v in edges2:
graph2[u].append(v)
graph2[v].append(u)
def bfs(graph, start, limit):
if limit < 0:
return 0
visited = [False] * len(graph)
visited[start] = True
queue = deque([(start, 0)])
count = 0
while queue:
node, dist = queue.popleft()
if dist > limit:
continue
count += 1
for nei in graph[node]:
if not visited[nei]:
visited[nei] = True
queue.append((nei, dist + 1))
return count
first = [bfs(graph1, i, k) for i in range(n)]
best = max(bfs(graph2, i, k - 1) for i in range(m))
return [x + best for x in first]
sol = Solution()
# Example 1
assert sol.maxTargetNodes(
[[0,1],[0,2],[2,3],[2,4]],
[[0,1],[0,2],[0,3],[2,7],[1,4],[4,5],[4,6]],
2
) == [9,7,9,8,8]
# Example 2
assert sol.maxTargetNodes(
[[0,1],[0,2],[0,3],[0,4]],
[[0,1],[1,2],[2,3]],
1
) == [6,3,3,3,3]
# k = 0, cannot reach second tree
assert sol.maxTargetNodes(
[[0,1]],
[[0,1]],
0
) == [1,1]
# Smallest valid trees
assert sol.maxTargetNodes(
[[0,1]],
[[0,1]],
1
) == [3,3]
# Chain trees
assert sol.maxTargetNodes(
[[0,1],[1,2],[2,3]],
[[0,1],[1,2]],
2
) == [5,6,6,5]
# Star-shaped tree
assert sol.maxTargetNodes(
[[0,1],[0,2],[0,3]],
[[0,1],[0,2],[0,3]],
1
) == [5,3,3,3]
# Large k reaching entire trees
assert sol.maxTargetNodes(
[[0,1],[1,2]],
[[0,1],[1,2],[2,3]],
10
) == [7,7,7]
print("All tests passed!")
Test Summary
| Test | Why |
|---|---|
| Example 1 | Validates general mixed tree structure |
| Example 2 | Tests star tree and small distance |
| k = 0 | Ensures second tree contributes nothing |
| Smallest valid trees | Verifies minimum constraints |
| Chain trees | Tests deep linear traversal |
| Star-shaped tree | Tests highly connected center |
| Large k | Ensures full-tree reachability |
Edge Cases
Case 1: k = 0
When k = 0, a node can only reach itself.
This is tricky because entering the second tree requires crossing one edge, which already exceeds the allowed distance.
A buggy implementation might accidentally count one node from the second tree anyway.
The implementation handles this cleanly because BFS immediately returns 0 whenever the limit becomes negative:
if limit < 0:
return 0
So the second tree contributes nothing.
Case 2: Extremely Unbalanced Trees
A chain-shaped tree can create large distances between nodes.
For example:
0 - 1 - 2 - 3 - 4
Some naive implementations incorrectly assume balanced traversal depth or use recursive DFS that risks recursion depth issues.
This solution uses iterative BFS, which safely handles all tree shapes.
Case 3: Large k
If k exceeds the diameter of the tree, every node becomes reachable.
A buggy implementation might continue unnecessary traversal logic or incorrectly prune nodes.
Since BFS only checks:
if dist > limit:
continue
all nodes are naturally counted once the distance limit is large enough.
Case 4: Optimal Bridge Placement
A common mistake is trying all bridge endpoints in tree 1.
The key observation is that connecting directly from node i is always optimal because every extra step inside tree 1 wastes distance budget.
The implementation encodes this insight mathematically by independently computing:
- nodes reachable within
kin tree 1 - nodes reachable within
k - 1in tree 2
This guarantees the optimal bridge choice is always considered implicitly. k =