Number Of Ways To Reconstruct A Tree solution and explanation
2 more videos on this problem
The problem
You are given an array pairs, where pairs[i] = [xi, yi], and:
- There are no duplicates.
xi < yi
Let ways be the number of rooted trees that satisfy the following conditions:
- The tree consists of nodes whose values appeared in
pairs. - A pair
[xi, yi]exists inpairsif and only ifxiis an ancestor ofyioryiis an ancestor ofxi. - Note: the tree does not have to be a binary tree.
Two ways are considered to be different if there is at least one node that has different parents in both ways.
Return:
0ifways == 01ifways == 12ifways > 1
A rooted tree is a tree that has a single root node, and all edges are oriented to be outgoing from the root.
An ancestor of a node is any node on the path from the root to that node (excluding the node itself). The root has no ancestors.
Example 1:
Input: pairs = [[1,2],[2,3]] Output: 1 Explanation: There is exactly one valid rooted tree, which is shown in the above figure.
Example 2:
Input: pairs = [[1,2],[2,3],[1,3]] Output: 2 Explanation: There are multiple valid rooted trees. Three of them are shown in the above figures.
Example 3:
Input: pairs = [[1,2],[2,3],[2,4],[1,5]] Output: 0 Explanation: There are no valid rooted trees.
Constraints:
1 <= pairs.length <= 1051 <= xi < yi <= 500- The elements in
pairsare unique.
Approach overview
Problem Overview: You receive a list of node pairs that represent ancestor relationships in an unknown tree. The task is to determine whether these pairs can reconstruct a valid tree and count how many distinct trees satisfy all relationships. The answer is 0 if reconstruction is impossible, 1 if the tree is uniquely determined, and 2 if multiple valid trees exist.
Approach 1: Union-Find with Degree Counting (O(n^2) time, O(n^2) space)
Treat every pair as an undirected connection and build an adjacency structure. The key observation: in a valid reconstruction, the root must be connected to every other node, meaning its degree equals n - 1. After identifying the root, process nodes by descending degree and assign each node a parent among its neighbors with a higher or equal degree. Union-Find helps maintain connectivity while validating that ancestor relationships don't contradict earlier assignments. If two candidate parents share identical degrees and neighbor sets, multiple reconstructions exist, so return 2. This approach works well when reasoning about connectivity and component merging using Union-Find.
Approach 2: Graph Construction Verification (O(n^2) time, O(n^2) space)
Build a graph where each node stores all neighbors from the pair list. Select the root as the node connected to all others. For every remaining node, search among its neighbors for the parent candidate with the smallest degree that is still larger than or equal to the node's degree. Verify that the node's neighbor set (excluding the parent) is contained within the parent's neighbor set. This ensures the parent truly dominates the subtree in the ancestor hierarchy. If any neighbor constraint fails, the reconstruction is impossible. If a node and its parent have the same degree, there are multiple valid parent assignments, so the answer becomes 2. This solution directly validates structural constraints of tree reconstruction using graph adjacency relationships.
Recommended for interviews: The graph construction verification approach is typically expected. It clearly models the ancestor relationships and shows you understand how node degrees reveal parent-child hierarchy. A brute reasoning approach helps explain the constraints, but implementing the adjacency validation with degree ordering demonstrates stronger algorithmic skill.
Approach 1: Union-Find with Degree Counting
This approach uses a Union-Find (Disjoint Set Union) data structure to track connected components and determine ancestries recursively. Additionally, maintain a degree count for each node to find potential roots and ensure the tree is connected.
If there's a node with a degree equal to the number of nodes minus one, it's the root. If more than one such node exists, there are multiple possible trees.
class Solution:
def checkWays(self, pairs):
from collections import defaultdict
import functools
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
rootX = find(x)
rootY = find(y)
if rootX != rootY:
parent[rootY] = rootX
nodes = set()
for x, y in pairs:
nodes.add(x)
nodes.add(y)
parent = {x: x for x in nodes}
adjacency = defaultdict(set)
degree = defaultdict(int)
for x, y in pairs:
adjacency[x].add(y)
adjacency[y].add(x)
degree[x] += 1
degree[y] += 1
for x, y in pairs:
union(x, y)
root_candidate = [x for x in nodes if degree[x] == len(nodes) - 1]
if len(root_candidate) == 0: # can't have a tree
return 0
if len(root_candidate) > 1: # multiple trees possible
return 2
root = root_candidate[0]
groups = defaultdict(set)
for x in nodes:
groups[find(x)].add(x)
# Check if all nodes except root are connected to it through some path
for key, group in groups.items():
if root not in group and len(group) > 1:
return 0
return 1var checkWays = function(pairs) {
let adj = new Map();
let degree = new Map();
let nodes = new Set();
for (let [u, v] of pairs) {
if (!adj.has(u)) adj.set(u, new Set());
if (!adj.has(v)) adj.set(v, new Set());
adj.get(u).add(v);
adj.get(v).add(u);
degree.set(u, (degree.get(u) || 0) + 1);
degree.set(v, (degree.get(v) || 0) + 1);
nodes.add(u);
nodes.add(v);
}
const roots = [];
for (let node of nodes) {
if (degree.get(node) === nodes.size - 1) {
roots.push(node);
}
}
if (roots.length > 1) return 2;
if (roots.length === 0) return 0;
const root = roots[0];
const visited = new Set();
const dfs = (node) => {
visited.add(node);
for (let neighbor of adj.get(node)) {
if (!visited.has(neighbor)) {
dfs(neighbor);
}
}
};
dfs(root);
return visited.size === nodes.size ? 1 : 0;
};This code implements a union-find structure to connect nodes based on the pairs given. It finds the number of possible ways to create a tree from these pairs by analyzing the degree of each node. If there's one node with a degree equal to the total number of nodes minus one, it can potentially be the root.
Time Complexity: O(N) where N is the number of nodes, because each of the operations in the union-find has nearly constant time complexity.
Space Complexity: O(N) due to storing nodes, their degrees, and adjacency list.
Approach 2: Graph Construction Verification
This method involves constructing the graph represented by the pairs, maintaining an adjacency list, and validating the graph's tree structure potential. Specifically, ensure the graph has a single connected component and checks if the degree conditions for trees are met.
import java.util.*;
public class Solution {
public int checkWays(int[][] pairs) {
Map<Integer, Set<Integer>> adj = new HashMap<>();
Map<Integer, Integer> degree = new HashMap<>();
Set<Integer> nodes = new HashSet<>();
for (int[] pair : pairs) {
adj.computeIfAbsent(pair[0], k -> new HashSet<>()).add(pair[1]);
adj.computeIfAbsent(pair[1], k -> new HashSet<>()).add(pair[0]);
degree.put(pair[0], degree.getOrDefault(pair[0], 0) + 1);
degree.put(pair[1], degree.getOrDefault(pair[1], 0) + 1);
nodes.add(pair[0]);
nodes.add(pair[1]);
}
List<Integer> possibleRoots = new ArrayList<>();
for (int node : nodes) {
if (degree.get(node) == nodes.size() - 1) {
possibleRoots.add(node);
}
}
if (possibleRoots.size() == 0) return 0;
if (possibleRoots.size() > 1) return 2;
int root = possibleRoots.get(0);
Set<Integer> visited = new HashSet<>();
dfs(root, adj, visited);
return visited.size() == nodes.size() ? 1 : 0;
}
private void dfs(int node, Map<Integer, Set<Integer>> adj, Set<Integer> visited) {
visited.add(node);
for (int neighbor : adj.get(node)) {
if (!visited.contains(neighbor)) {
dfs(neighbor, adj, visited);
}
}
}
}#include <vector>
#include <unordered_map>
#include <unordered_set>
using namespace std;
class Solution {
public:
int checkWays(vector<vector<int>>& pairs) {
unordered_map<int, unordered_set<int>> adj;
unordered_map<int, int> degree;
unordered_set<int> nodes;
for (auto& pair : pairs) {
adj[pair[0]].insert(pair[1]);
adj[pair[1]].insert(pair[0]);
degree[pair[0]]++;
degree[pair[1]]++;
nodes.insert(pair[0]);
nodes.insert(pair[1]);
}
vector<int> roots;
for (auto& node : nodes) {
if (degree[node] == nodes.size() - 1) {
roots.push_back(node);
}
}
if (roots.empty()) return 0;
if (roots.size() > 1) return 2;
int root = roots[0];
unordered_set<int> visited;
dfs(root, adj, visited);
return visited.size() == nodes.size() ? 1 : 0;
}
private:
void dfs(int node, unordered_map<int, unordered_set<int>>& adj, unordered_set<int>& visited) {
visited.insert(node);
for (auto& neighbor : adj[node]) {
if (visited.find(neighbor) == visited.end()) {
dfs(neighbor, adj, visited);
}
}
}
};This Java implementation builds adjacency lists and checks degree conditions to identify potential root nodes. If an appropriate root is found, a DFS determines whether a single connected component includes all nodes, confirming a valid tree structure.
Time Complexity: O(n + m) where n is the number of nodes and m is the number of pairs.
Space Complexity: O(n + m) due to adjacency and degree maps' storage requirements.
Approach 3: Default Approach
func checkWays(pairs [][]int) int {
g := make([][]bool, 510)
v := make([][]int, 510)
for i := range g {
g[i] = make([]bool, 510)
}
for _, p := range pairs {
x, y := p[0], p[1]
g[x][y] = true
g[y][x] = true
v[x] = append(v[x], y)
v[y] = append(v[y], x)
}
var nodes []int
for i := 1; i <= 500; i++ {
if len(v[i]) > 0 {
nodes = append(nodes, i)
g[i][i] = true
}
}
sort.Slice(nodes, func(i, j int) bool {
return len(v[nodes[i]]) < len(v[nodes[j]])
})
equal := false
root := 0
for i, x := range nodes {
j := i + 1
for ; j < len(nodes) && !g[x][nodes[j]]; j++ {
}
if j < len(nodes) {
y := nodes[j]
if len(v[x]) == len(v[y]) {
equal = true
}
for _, z := range v[x] {
if !g[y][z] {
return 0
}
}
} else {
root++
}
}
if root > 1 {
return 0
}
if equal {
return 2
}
return 1
}Complexity comparison
| Approach | Complexity |
|---|---|
| Union-Find with Degree Counting | Time Complexity: O(N) where N is the number of nodes, because each of the operations in the union-find has nearly constant time complexity. |
| Graph Construction Verification | Time Complexity: O(n + m) where n is the number of nodes and m is the number of pairs. |
| Default Approach | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Union-Find with Degree Counting | O(n^2) | O(n^2) | Useful when modeling pair connectivity and verifying ancestor consistency with disjoint sets |
| Graph Construction Verification | O(n^2) | O(n^2) | General case; preferred interview solution for validating parent-child hierarchy |
Frequently asked questions
Is Number Of Ways To Reconstruct A Tree easy or hard?
Number Of Ways To Reconstruct A Tree is classified as a Hard problem. The difficulty comes from recognizing how node degrees reveal the root and parent hierarchy, then verifying that neighbor relationships satisfy ancestor constraints while detecting multiple valid reconstructions.
Number Of Ways To Reconstruct A Tree Python/Java solution
Typical implementations build adjacency sets using hash maps or sets in Python, Java, C++, or JavaScript. Nodes are processed by degree to identify parents and validate subset relationships among neighbors. Both Union-Find based validation and direct graph verification approaches can be implemented across these languages with O(n^2) complexity.
How to solve Number Of Ways To Reconstruct A Tree in O(n)?
An O(n) solution is generally not feasible because the algorithm must compare neighbor relationships between nodes to verify ancestor constraints. Those checks require examining adjacency sets, which leads to O(n^2) work in the worst case. Practical solutions therefore target O(n^2) complexity using graph validation or degree ordering.
What is the best approach for Number Of Ways To Reconstruct A Tree?
Graph construction with degree-based parent selection is the most common approach. Build adjacency sets from the pairs, identify the root with degree n-1, and assign each node a parent whose degree is the smallest valid superset of neighbors. During verification, identical degrees between node and parent indicate multiple valid reconstructions. The overall complexity is O(n^2).
Is Number Of Ways To Reconstruct A Tree asked at Google/Amazon/Meta?
Hard graph and tree reconstruction problems like this appear in interviews at companies such as Google, Meta, and Amazon. The question tests understanding of graph modeling, node degree reasoning, and structural validation in trees. Interviewers expect a correct O(n^2) solution with clear reasoning about parent selection.
What data structure is used in Number Of Ways To Reconstruct A Tree?
The solution primarily uses adjacency sets or hash maps to represent the graph built from node pairs. Additional structures include degree counts, parent tracking, and sometimes Union-Find for connectivity validation. These structures allow efficient neighbor checks and hierarchy verification.
What is the time complexity of Number Of Ways To Reconstruct A Tree?
Most optimal implementations run in O(n^2) time with O(n^2) space. The algorithm builds adjacency sets from the pair list and repeatedly checks neighbor containment when assigning parents. Because neighbor comparisons can take O(n) and occur for multiple nodes, the total cost becomes quadratic.



