Validate Binary Tree Nodes solution and explanation
9 more videos on this problem
Validate Binary Tree Nodes | Using Binary Tree Properties | BFS | META | Leetcode - 1361codestorywithMIK · 24:42 · 13K views
Validate Binary Tree Nodes - Leetcode 1361 - PythonNeetCodeIO · 9:55 · 12K views
Validate Binary Tree Nodes (Leetcode 1361)Coding Interviews · 10:33 · 5K views
Validate Binary Tree Nodes | Using DSU | Intuitive | META | Leetcode - 1361codestorywithMIK · 21:13 · 5K views
VALIDATE BINARY TREE NODES | LEETCODE # 1361 | PYTHON SOLUTIONCracking FAANG · 21:41 · 2K views
1361. Validate Binary Tree Nodes (Leetcode Medium)Programming Live with Larry · 9:26 · 1K views
1361 || Validate Binary Tree Nodes || C++ || Leetcode Daily ChallengeCodeFreaks · 16:06 · 533 views
Daily Leetcode #1295: Oct 17, 2023 - 1361. Validate Binary Tree NodesProgramming Live with Larry · 9:51 · 355 views
✅ Validate Binary Tree Nodes ✅ Simplest Possible Solution ✅ BFS ✅ Binary Tree ✅ LeetCode 1361 MediumLeet Logics · 3:32 · 350 views
LeetCode 1361: ✅ Validate Binary Tree NodesThe algorithm Architect · 11:51 · 237 views
The problem
You have n binary tree nodes numbered from 0 to n - 1 where node i has two children leftChild[i] and rightChild[i], return true if and only if all the given nodes form exactly one valid binary tree.
If node i has no left child then leftChild[i] will equal -1, similarly for the right child.
Note that the nodes have no values and that we only use the node numbers in this problem.
Example 1:
Input: n = 4, leftChild = [1,-1,3,-1], rightChild = [2,-1,-1,-1] Output: true
Example 2:
Input: n = 4, leftChild = [1,-1,3,-1], rightChild = [2,3,-1,-1] Output: false
Example 3:
Input: n = 2, leftChild = [1,0], rightChild = [-1,-1] Output: false
Constraints:
n == leftChild.length == rightChild.length1 <= n <= 104-1 <= leftChild[i], rightChild[i] <= n - 1
Approach overview
Problem Overview: You are given n nodes labeled 0..n-1 and two arrays leftChild and rightChild. Each index describes the left and right child of a node. The task is to verify whether these relationships form exactly one valid binary tree: one root, no node with multiple parents, no cycles, and all nodes connected.
The constraints make the problem easier to think about as a graph validation task rather than building an explicit tree. A valid binary tree must satisfy three properties: every node except the root has exactly one parent, there are no cycles, and all nodes belong to one connected component.
Approach 1: Disjoint Set Union (Union-Find) (O(n) time, O(n) space)
This method treats the structure as a directed graph and uses Union Find to detect cycles and multiple parents while connecting nodes. Iterate through each node and attempt to union it with its left and right children. If a child already belongs to the same set, a cycle exists. Maintain an array tracking whether a node already has a parent; if a second parent appears, the structure is invalid. After processing all edges, ensure exactly one connected component remains.
The key insight: a valid binary tree with n nodes must have exactly n-1 edges and remain fully connected without forming cycles. Union-Find efficiently enforces these conditions while processing edges once.
Approach 2: In-degree + Depth-First Search (DFS) (O(n) time, O(n) space)
This approach models the problem as a graph validation task using in-degree counting and traversal. First compute the in-degree of every node by scanning leftChild and rightChild. If any node reaches in-degree greater than 1, two parents exist and the structure cannot be a valid tree. The node with in-degree 0 must be the root; if there are zero or multiple such nodes, the tree is invalid.
Once the root is identified, run a Depth-First Search starting from that root. Track visited nodes while traversing left and right children. If DFS encounters a previously visited node, a cycle exists. After traversal, verify that all n nodes were visited, ensuring the structure is fully connected.
This approach mirrors how interviewers typically reason about trees: identify the root, enforce the single-parent rule with in-degree, then verify reachability with DFS.
Recommended for interviews: The in-degree + DFS approach is usually expected. It directly checks the defining properties of a tree: a single root, no node with multiple parents, and full connectivity. The Union-Find approach is also valid and shows strong understanding of graph cycle detection, which can stand out in system-heavy interviews.
Approach 1: Disjoint Set Union (DSU)
To solve this problem using the Disjoint Set Union (DSU) approach, we aim to union nodes based on their parent-child relationships. A valid tree should follow these rules:
- There should be exactly one node with no parent, which serves as a root, ensuring all nodes belong to one connected component.
- A tree should not have cyclic dependencies between nodes.
- We use a DSU to union nodes and check for cycles. Also, we'll use an array to track nodes' parents, identifying the root node.
class Solution:
def find(self, parent, x):
if parent[x] != x:
parent[x] = self.find(parent, parent[x])
return parent[x]
def union(self, parent, x, y):
rootX = self.find(parent, x)
rootY = self.find(parent, y)
if rootX == rootY:
return False
parent[rootY] = rootX
return True
def validateBinaryTreeNodes(self, n, leftChild, rightChild):
parent = list(range(n))
hasParent = [0] * n
for i in range(n):
if leftChild[i] != -1:
if hasParent[leftChild[i]] or not self.union(parent, i, leftChild[i]):
return False
hasParent[leftChild[i]] = 1
if rightChild[i] != -1:
if hasParent[rightChild[i]] or not self.union(parent, i, rightChild[i]):
return False
hasParent[rightChild[i]] = 1
return hasParent.count(0) == 1class Solution {
public int find(int[] parent, int x) {
if (parent[x] != x) {
parent[x] = find(parent, parent[x]);
}
return parent[x];
}
public boolean unionFind(int[] parent, int x, int y) {
int rootX = find(parent, x);
int rootY = find(parent, y);
if (rootX == rootY) return false;
parent[rootY] = rootX;
return true;
}
public boolean validateBinaryTreeNodes(int n, int[] leftChild, int[] rightChild) {
int[] parent = new int[n];
int[] hasParent = new int[n];
for (int i = 0; i < n; ++i) {
parent[i] = i;
}
for (int i = 0; i < n; ++i) {
if (leftChild[i] != -1) {
if (hasParent[leftChild[i]] == 1 || !unionFind(parent, i, leftChild[i])) {
return false;
}
hasParent[leftChild[i]] = 1;
}
if (rightChild[i] != -1) {
if (hasParent[rightChild[i]] == 1 || !unionFind(parent, i, rightChild[i])) {
return false;
}
hasParent[rightChild[i]] = 1;
}
}
int rootCount = 0;
for (int i = 0; i < n; ++i) {
if (hasParent[i] == 0) {
rootCount++;
}
}
return rootCount == 1;
}
}#include <vector>
#include <unordered_set>
class DSU {
std::vector<int> parent;
public:
DSU(int n) {
parent.resize(n);
for (int i = 0; i < n; ++i) parent[i] = i;
}
int find(int x) {
if (parent[x] != x)
parent[x] = find(parent[x]);
return parent[x];
}
bool unionSets(int x, int y) {
int rootX = find(x), rootY = find(y);
if (rootX == rootY) return false;
parent[rootY] = rootX;
return true;
}
};
bool validateBinaryTreeNodes(int n, std::vector<int>& leftChild, std::vector<int>& rightChild) {
DSU dsu(n);
std::vector<int> inDegree(n, 0);
for (int i = 0; i < n; ++i) {
if (leftChild[i] != -1) {
if (++inDegree[leftChild[i]] > 1 || !dsu.unionSets(i, leftChild[i]))
return false;
}
if (rightChild[i] != -1) {
if (++inDegree[rightChild[i]] > 1 || !dsu.unionSets(i, rightChild[i]))
return false;
}
}
int rootCount = 0;
for (int i = 0; i < n; ++i) {
if (dsu.find(i) == i) ++rootCount;
}
return rootCount == 1;
}var validateBinaryTreeNodes = function(n, leftChild, rightChild) {
const parent = Array.from({ length: n }, (_, index) => index);
const find = (x) => {
if (parent[x] !== x) {
parent[x] = find(parent[x]);
}
return parent[x];
};
const union = (x, y) => {
const rootX = find(x);
const rootY = find(y);
if (rootX === rootY) return false;
parent[rootY] = rootX;
return true;
};
const hasParent = new Array(n).fill(0);
for (let i = 0; i < n; ++i) {
if (leftChild[i] !== -1) {
if (hasParent[leftChild[i]] === 1 || !union(i, leftChild[i])) {
return false;
}
hasParent[leftChild[i]] = 1;
}
if (rightChild[i] !== -1) {
if (hasParent[rightChild[i]] === 1 || !union(i, rightChild[i])) {
return false;
}
hasParent[rightChild[i]] = 1;
}
}
return hasParent.filter(parent => parent === 0).length === 1;
};#include <stdbool.h>
int find(int* parent, int x) {
if (parent[x] != x) {
parent[x] = find(parent, parent[x]); // Path compression
}
return parent[x];
}
bool unionFind(int* parent, int x, int y) {
int rootX = find(parent, x);
int rootY = find(parent, y);
if (rootX == rootY) return false; // Cycle detected
parent[rootY] = rootX; // Union
return true;
}
bool validateBinaryTreeNodes(int n, int* leftChild, int leftChildSize, int* rightChild, int rightChildSize) {
int parent[n];
for (int i = 0; i < n; ++i) {
parent[i] = i;
}
int hasParent[n];
for (int i = 0; i < n; ++i) {
hasParent[i] = 0;
}
for (int i = 0; i < n; ++i) {
if (leftChild[i] != -1) {
if (hasParent[leftChild[i]] || !unionFind(parent, i, leftChild[i])) {
return false;
}
hasParent[leftChild[i]] = 1;
}
if (rightChild[i] != -1) {
if (hasParent[rightChild[i]] || !unionFind(parent, i, rightChild[i])) {
return false;
}
hasParent[rightChild[i]] = 1;
}
}
int rootCount = 0;
for (int i = 0; i < n; ++i) {
if (!hasParent[i]) {
++rootCount;
}
}
return rootCount == 1;
}public class Solution {
public int Find(int[] parent, int x) {
if (parent[x] != x) {
parent[x] = Find(parent, parent[x]);
}
return parent[x];
}
public bool Union(int[] parent, int x, int y) {
int rootX = Find(parent, x);
int rootY = Find(parent, y);
if (rootX == rootY) return false;
parent[rootY] = rootX;
return true;
}
public bool ValidateBinaryTreeNodes(int n, int[] leftChild, int[] rightChild) {
int[] parent = new int[n];
int[] hasParent = new int[n];
for (int i = 0; i < n; ++i) parent[i] = i;
for (int i = 0; i < n; ++i) {
if (leftChild[i] != -1) {
if (hasParent[leftChild[i]] == 1 || !Union(parent, i, leftChild[i])) {
return false;
}
hasParent[leftChild[i]] = 1;
}
if (rightChild[i] != -1) {
if (hasParent[rightChild[i]] == 1 || !Union(parent, i, rightChild[i])) {
return false;
}
hasParent[rightChild[i]] = 1;
}
}
int rootCount = 0;
for (int i = 0; i < n; ++i) {
if (hasParent[i] == 0) {
rootCount++;
}
}
return rootCount == 1;
}
}The Python solution follows the union-find pattern, organized into find and union methods. The parent list captures each node’s representative root, while hasParent ensures a node has at most one parent. Disjoint set operations verify no cycles and ensure all nodes unify under one connected root without duplication errors, confirming only one root node.
Time Complexity: O(n), attributed to efficient union and find operations.
Space Complexity: O(n) due to arrays storing node relationships.
Approach 2: In-degree with Depth-First Search (DFS)
This approach involves calculating the in-degree of each node and checking connectivity via a DFS. The key aspects of a tree like single-root presence and cycle-checking can be managed by:
- Counting in-degrees with the premise that a tree's nodes (except the root) have exactly one parent.
- Deploying a DFS to ensure all nodes are reachable from a root node, confirming no cycles are involved.
- Checking if we can visit exactly all the nodes starting from the found root node.
class Solution:
def dfs(self, node, visited, children):
if visited[node]: return
visited[node] = True
if children[0][node] != -1:
self.dfs(children[0][node], visited, children)
if children[1][node] != -1:
self.dfs(children[1][node], visited, children)
def validateBinaryTreeNodes(self, n, leftChild, rightChild):
# Calculate in-degrees
inDegree = [0] * n
for i in range(n):
if leftChild[i] != -1:
inDegree[leftChild[i]] += 1
if rightChild[i] != -1:
inDegree[rightChild[i]] += 1
root = -1
for i in range(n):
if inDegree[i] == 0:
if root == -1:
root = i
else:
return False
if root == -1:
return False
visited = [False] * n
self.dfs(root, visited, [leftChild, rightChild])
return all(visited)class Solution {
private void dfs(int node, boolean[] visited, int[][] children) {
if (visited[node]) return;
visited[node] = true;
if (children[0][node] != -1) dfs(children[0][node], visited, children);
if (children[1][node] != -1) dfs(children[1][node], visited, children);
}
public boolean validateBinaryTreeNodes(int n, int[] leftChild, int[] rightChild) {
int[] inDegree = new int[n];
int root = -1;
for (int i = 0; i < n; ++i) {
if (leftChild[i] != -1) inDegree[leftChild[i]]++;
if (rightChild[i] != -1) inDegree[rightChild[i]]++;
}
for (int i = 0; i < n; ++i) {
if (inDegree[i] == 0) {
if (root == -1) {
root = i;
} else {
return false;
}
}
}
if (root == -1) return false;
boolean[] visited = new boolean[n];
dfs(root, visited, new int[][]{leftChild, rightChild});
for (boolean v : visited) {
if (!v) return false;
}
return true;
}
}#include <vector>
void dfs(int node, std::vector<bool>& visited, std::vector<std::vector<int>>& adj) {
if (visited[node]) return;
visited[node] = true;
for (int child : adj[node]) {
dfs(child, visited, adj);
}
}
bool validateBinaryTreeNodes(int n, std::vector<int>& leftChild, std::vector<int>& rightChild) {
std::vector<int> inDegree(n, 0);
std::vector<std::vector<int>> adj(n);
for (int i = 0; i < n; ++i) {
if (leftChild[i] != -1) {
adj[i].push_back(leftChild[i]);
inDegree[leftChild[i]]++;
}
if (rightChild[i] != -1) {
adj[i].push_back(rightChild[i]);
inDegree[rightChild[i]]++;
}
}
int root = -1;
for (int i = 0; i < n; ++i) {
if (inDegree[i] == 0) {
if (root == -1) {
root = i;
} else {
return false;
}
}
}
if (root == -1) return false;
std::vector<bool> visited(n, false);
dfs(root, visited, adj);
for (auto v : visited) {
if (!v) return false;
}
return true;
}var validateBinaryTreeNodes = function(n, leftChild, rightChild) {
const inDegree = new Array(n).fill(0);
for (let i = 0; i < n; i++) {
if (leftChild[i] !== -1) inDegree[leftChild[i]]++;
if (rightChild[i] !== -1) inDegree[rightChild[i]]++;
}
let root = -1;
for (let i = 0; i < n; i++) {
if (inDegree[i] === 0) {
if (root === -1) {
root = i;
} else {
return false; // more than one root
}
}
}
if (root === -1) return false; // no root
const visited = new Array(n).fill(false);
const dfs = (node) => {
if (visited[node]) return;
visited[node] = true;
if (leftChild[node] !== -1) dfs(leftChild[node]);
if (rightChild[node] !== -1) dfs(rightChild[node]);
};
dfs(root);
return visited.every(v => v);
};#include <stdbool.h>
#include <string.h>
void dfs(int node, int* visited, int** children, int n) {
if (visited[node]) return;
visited[node] = 1;
if (children[0][node] != -1) dfs(children[0][node], visited, children, n);
if (children[1][node] != -1) dfs(children[1][node], visited, children, n);
}
bool validateBinaryTreeNodes(int n, int* leftChild, int leftChildSize, int* rightChild, int rightChildSize) {
int inDegree[n];
memset(inDegree, 0, sizeof(inDegree));
for (int i = 0; i < n; ++i) {
if (leftChild[i] != -1) ++inDegree[leftChild[i]];
if (rightChild[i] != -1) ++inDegree[rightChild[i]];
}
int root = -1;
for (int i = 0; i < n; ++i) {
if (inDegree[i] == 0) {
if (root == -1) {
root = i;
} else {
return false; // more than one root
}
}
}
if (root == -1) return false; // no root
int visited[n];
memset(visited, 0, sizeof(visited));
dfs(root, visited, (int*[]){leftChild, rightChild}, n);
for (int i = 0; i < n; ++i) {
if (!visited[i]) return false; // not fully connected
}
return true;
}public class Solution {
private void Dfs(int node, bool[] visited, int[][] children) {
if (visited[node]) return;
visited[node] = true;
if (children[0][node] != -1) Dfs(children[0][node], visited, children);
if (children[1][node] != -1) Dfs(children[1][node], visited, children);
}
public bool ValidateBinaryTreeNodes(int n, int[] leftChild, int[] rightChild) {
int[] inDegree = new int[n];
for (int i = 0; i < n; i++) {
if (leftChild[i] != -1) inDegree[leftChild[i]]++;
if (rightChild[i] != -1) inDegree[rightChild[i]]++;
}
int root = -1;
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
if (root == -1) {
root = i;
} else {
return false;
}
}
}
if (root == -1) return false;
bool[] visited = new bool[n];
Dfs(root, visited, new int[][] { leftChild, rightChild });
foreach (bool v in visited) {
if (!v) return false;
}
return true;
}
}The Python strategem frames nodes as tuples within a list structure, exploiting recognized semantics over positions within binary arrays (left and right children). Node in-degrees determine the hierarchy without multi-parents obstruction, generating a singular possible valid root. DFS confirms full graph traversability from the root node, ensuring a connected nature without interruptions.
Time Complexity: O(n), realized as an entire resolution of nodes from in-degree positioning and DFS propagation.
Space Complexity: O(n), reflecting iterative markers over seen nodes and in-degree counts necessary to thread parent-child relations.
Approach 3: Union-Find
We can traverse each node i and its corresponding left and right children l, r, using an array vis to record whether the node has a parent:
- If the child node already has a parent, it means there are multiple fathers, which does not meet the condition, so we return
falsedirectly. - If the child node and the parent node are already in the same connected component, it means a cycle will be formed, which does not meet the condition, so we return
falsedirectly. - Otherwise, we perform a union operation, set the corresponding position of the
visarray totrue, and decrease the number of connected components by1.
After the traversal, we check whether the number of connected components in the union-find set is 1. If it is, we return true, otherwise, we return false.
The time complexity is O(n times \alpha(n)), and the space complexity is O(n). Where n is the number of nodes, and \alpha(n) is the inverse Ackermann function, which is less than 5.
func validateBinaryTreeNodes(n int, leftChild []int, rightChild []int) bool {
p := make([]int, n)
for i := range p {
p[i] = i
}
var find func(int) int
find = func(x int) int {
if p[x] != x {
p[x] = find(p[x])
}
return p[x]
}
vis := make([]bool, n)
for i, a := range leftChild {
for _, j := range []int{a, rightChild[i]} {
if j != -1 {
if vis[j] || find(i) == find(j) {
return false
}
p[find(i)] = find(j)
vis[j] = true
n--
}
}
}
return n == 1
}Complexity comparison
| Approach | Complexity |
|---|---|
| Disjoint Set Union (DSU) | Time Complexity: O(n), attributed to efficient union and find operations. Space Complexity: O(n) due to arrays storing node relationships. |
| In-degree with Depth-First Search (DFS) | Time Complexity: O(n), realized as an entire resolution of nodes from in-degree positioning and DFS propagation. Space Complexity: O(n), reflecting iterative markers over seen nodes and in-degree counts necessary to thread parent-child relations. |
| Union-Find | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Disjoint Set Union (Union-Find) | O(n) | O(n) | When treating the problem as graph connectivity with cycle detection |
| In-degree + DFS Traversal | O(n) | O(n) | Most intuitive interview solution for validating tree structure |
Frequently asked questions
Is Validate Binary Tree Nodes easy or hard?
LeetCode classifies this problem as Medium. The difficulty comes from recognizing that the arrays represent a directed graph and that a valid binary tree must satisfy three constraints: one root, no node with multiple parents, and no cycles while remaining fully connected.
How to solve Validate Binary Tree Nodes in O(n)?
Scan the leftChild and rightChild arrays to compute in-degrees and detect nodes with multiple parents. Identify the root as the only node with in-degree 0. Perform a DFS or BFS from that root and mark visited nodes. If you visit all n nodes and never encounter a previously visited node, the structure forms a valid binary tree.
What is the best approach for Validate Binary Tree Nodes?
The most common approach uses in-degree counting plus a DFS traversal. First ensure every node has at most one parent and identify the single root (node with in-degree 0). Then run DFS from that root to verify there are no cycles and all nodes are reachable. This runs in O(n) time and O(n) space.
What data structure is used in Validate Binary Tree Nodes?
Typical solutions rely on graph traversal structures such as adjacency relationships from the child arrays, a visited set for DFS, and an in-degree array. Some implementations use a Union-Find (Disjoint Set Union) structure to detect cycles and maintain connected components.
What is the time complexity of Validate Binary Tree Nodes?
Both common solutions run in O(n) time where n is the number of nodes. Each node and edge is processed at most once while checking parents and performing DFS or Union-Find operations. Space complexity is O(n) for tracking parents, visited nodes, or disjoint set structures.
Validate Binary Tree Nodes Python or Java solution approach
In Python or Java, compute an in-degree array while scanning child pointers. Reject nodes with in-degree greater than one. Find the root, then run DFS or BFS using a stack or queue to visit children. Ensure every node is visited exactly once to confirm the graph forms a valid binary tree.
Is Validate Binary Tree Nodes asked at Google, Amazon, or Meta?
Binary tree validation and graph cycle detection problems appear frequently in interviews at companies like Amazon, Google, and Meta. Variants often test detecting cycles, verifying tree structure, or ensuring single-parent constraints using DFS, BFS, or Union-Find.
