Talentd
FleetCode
Make and follow sheets.Sign in

Number of Connected Components in an Undirected Graph solution and explanation

MediumBreadth-First SearchDepth-First SearchGraphUnion Find6 min read64.9% acceptanceUpdated 23 Mar 2026Premium elsewhere, free here
Number of Connected Components in an Undirected Graph - Union Find - Leetcode 323 - PythonNeetCode · 15:59 · 270K viewsWatch on YouTube
9 more videos on this problem
  1. Number of Connected Components in an Undirected Graph - Union Find - Leetcode 323 - PythonNeetCode · 15:59 · 270K views
  2. Union Find Data Structure - Number of Connected Components in an Undirected Graph (LeetCode)AlgosWithMichael · 15:01 · 24K views
  3. [Java] Union Find | Solve Leetcode - Number of Connected Components in an Undirected GraphEric Programming · 15:21 · 5K views
  4. NUMBER OF CONNECTED COMPONENTS IN AN UNDIRECTED GRAPH | LEETCODE # 323 | PYTHON GRAPH SOLUTIONCracking FAANG · 16:40 · 5K views
  5. Leetcode 323 | Number of Connected Components in an Undirected Graph( DFS and Union Find approach)Tech Traversal · 20:58 · 2K views
  6. LeetCode 323. Number of Connected Components in an Undirected GraphHappy Coding · 42:14 · 1K views
  7. Number of Connected Components in an Undirected Graph - LeetCode 323 - JavaScriptAlgoJS · 6:58 · 1K views
  8. Microsoft Coding Interview Question | Leetcode 323 | #Connected Components in an Undirected GraphWorkWithGoogler · 11:49 · 1K views
  9. Leetcode 323. Number of Connected Components in an Undirected Graph | DFS | AMAZON Top 19% solutionNeedCode⚡ · 9:29 · 737 views
  10. Number of Connected Components in an Undirected Graph - Leetcode 323 - Blind 75 Explained - GraphsOff By One · 16:37 · 614 views

The problem

You have a graph of n nodes. You are given an integer n and an array edges where edges[i] = [ai, bi] indicates that there is an edge between ai and bi in the graph.

Return the number of connected components in the graph.

Example 1:

Input: n = 5, edges = [[0,1],[1,2],[3,4]]
Output: 2

Example 2:

Input: n = 5, edges = [[0,1],[1,2],[2,3],[3,4]]
Output: 1

Constraints:

  • 1 <= n <= 2000
  • 1 <= edges.length <= 5000
  • edges[i].length == 2
  • 0 <= ai <= bi < n
  • ai != bi
  • There are no repeated edges.

Approach overview

Problem Overview: You are given n nodes labeled from 0 to n-1 and a list of undirected edges. The task is to determine how many connected components exist in the graph. A connected component is a group of nodes where every node is reachable from any other node in that group.

Approach 1: Graph Traversal using DFS/BFS (O(n + m) time, O(n + m) space)

Build an adjacency list from the edge list, then traverse the graph. Iterate through every node. If a node has not been visited, start a traversal (either Depth-First Search or Breadth-First Search) from that node and mark all reachable nodes as visited. Each time you start a new traversal, you have discovered a new connected component. The adjacency list takes O(n + m) space where m is the number of edges. The traversal visits each node and edge once, giving O(n + m) time complexity. This approach works well when you already represent the graph explicitly and want a clear traversal-based solution.

Approach 2: Union-Find / Disjoint Set (O(n + m · α(n)) time, O(n) space)

The Union-Find data structure groups nodes into sets representing components. Initially, every node is its own parent. For each edge (u, v), perform a union(u, v) operation to merge their sets. Use path compression and union by rank to keep the structure shallow. After processing all edges, the number of distinct roots equals the number of connected components. The amortized cost of each operation is nearly constant, α(n) (inverse Ackermann function). This approach avoids building adjacency lists and is particularly efficient when edges arrive incrementally or when solving multiple connectivity queries.

Both strategies rely on the same insight: nodes belong to the same component if there exists a path between them in the graph. Traversal explicitly explores that path structure, while Union-Find tracks connectivity through set merges.

Recommended for interviews: DFS or BFS traversal is the most commonly expected solution because it demonstrates understanding of graph representation and traversal. Union-Find is also highly valued since it shows familiarity with a powerful connectivity data structure. A candidate who can explain both approaches and their tradeoffs shows strong graph fundamentals.

Approach 1: DFS

First, we construct an adjacency list g based on the given edges, where g[i] represents all neighbor nodes of node i.

Then we traverse all nodes. For each node, we use DFS to traverse all its adjacent nodes and mark them as visited until all its adjacent nodes have been visited. In this way, we have found a connected component, and the answer is incremented by one. Then we continue to traverse the next unvisited node until all nodes have been visited.

The time complexity is O(n + m), and the space complexity is O(n + m). Where n and m are the number of nodes and edges, respectively.

class Solution:
    def countComponents(self, n: int, edges: List[List[int]]) -> int:
        def dfs(i: int) -> int:
            if i in vis:
                return 0
            vis.add(i)
            for j in g[i]:
                dfs(j)
            return 1

        g = [[] for _ in range(n)]
        for a, b in edges:
            g[a].append(b)
            g[b].append(a)
        vis = set()
        return sum(dfs(i) for i in range(n))
class Solution {
    private List<Integer>[] g;
    private boolean[] vis;

    public int countComponents(int n, int[][] edges) {
        g = new List[n];
        vis = new boolean[n];
        Arrays.setAll(g, k -> new ArrayList<>());
        for (var e : edges) {
            int a = e[0], b = e[1];
            g[a].add(b);
            g[b].add(a);
        }
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            ans += dfs(i);
        }
        return ans;
    }

    private int dfs(int i) {
        if (vis[i]) {
            return 0;
        }
        vis[i] = true;
        for (int j : g[i]) {
            dfs(j);
        }
        return 1;
    }
}
class Solution {
public:
    int countComponents(int n, vector<vector<int>>& edges) {
        vector<int> g[n];
        for (auto& e : edges) {
            int a = e[0], b = e[1];
            g[a].push_back(b);
            g[b].push_back(a);
        }
        vector<bool> vis(n);
        function<int(int)> dfs = [&](int i) {
            if (vis[i]) {
                return 0;
            }
            vis[i] = true;
            for (int j : g[i]) {
                dfs(j);
            }
            return 1;
        };
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            ans += dfs(i);
        }
        return ans;
    }
};
/**
 * @param {number} n
 * @param {number[][]} edges
 * @return {number}
 */
var countComponents = function (n, edges) {
    const g = Array.from({ length: n }, () => []);
    for (const [a, b] of edges) {
        g[a].push(b);
        g[b].push(a);
    }
    const vis = Array(n).fill(false);
    const dfs = i => {
        if (vis[i]) {
            return 0;
        }
        vis[i] = true;
        for (const j of g[i]) {
            dfs(j);
        }
        return 1;
    };
    return g.reduce((acc, _, i) => acc + dfs(i), 0);
};
function countComponents(n: number, edges: number[][]): number {
    const g: number[][] = Array.from({ length: n }, () => []);
    for (const [a, b] of edges) {
        g[a].push(b);
        g[b].push(a);
    }
    const vis: boolean[] = Array(n).fill(false);
    const dfs = (i: number): number => {
        if (vis[i]) {
            return 0;
        }
        vis[i] = true;
        for (const j of g[i]) {
            dfs(j);
        }
        return 1;
    };
    return g.reduce((acc, _, i) => acc + dfs(i), 0);
}
func countComponents(n int, edges [][]int) (ans int) {
	g := make([][]int, n)
	for _, e := range edges {
		a, b := e[0], e[1]
		g[a] = append(g[a], b)
		g[b] = append(g[b], a)
	}
	vis := make([]bool, n)
	var dfs func(int) int
	dfs = func(i int) int {
		if vis[i] {
			return 0
		}
		vis[i] = true
		for _, j := range g[i] {
			dfs(j)
		}
		return 1
	}
	for i := range g {
		ans += dfs(i)
	}
	return
}
Try this approach in the editor

Approach 2: Union-Find

We can use a union-find set to maintain the connected components in the graph.

First, we initialize a union-find set, then traverse all the edges. For each edge (a, b), we merge nodes a and b into the same connected component. If the merge is successful, it means that nodes a and b were not in the same connected component before, and the number of connected components decreases by one.

Finally, we return the number of connected components.

The time complexity is O(n + m times \alpha(n)), and the space complexity is O(n). Where n and m are the number of nodes and edges, respectively, and \alpha(n) is the inverse of the Ackermann function, which can be regarded as a very small constant.

class UnionFind:
    def __init__(self, n):
        self.p = list(range(n))
        self.size = [1] * n

    def find(self, x):
        if self.p[x] != x:
            self.p[x] = self.find(self.p[x])
        return self.p[x]

    def union(self, a, b):
        pa, pb = self.find(a), self.find(b)
        if pa == pb:
            return False
        if self.size[pa] > self.size[pb]:
            self.p[pb] = pa
            self.size[pa] += self.size[pb]
        else:
            self.p[pa] = pb
            self.size[pb] += self.size[pa]
        return True


class Solution:
    def countComponents(self, n: int, edges: List[List[int]]) -> int:
        uf = UnionFind(n)
        for a, b in edges:
            n -= uf.union(a, b)
        return n
class UnionFind {
    private final int[] p;
    private final int[] size;

    public UnionFind(int n) {
        p = new int[n];
        size = new int[n];
        for (int i = 0; i < n; ++i) {
            p[i] = i;
            size[i] = 1;
        }
    }

    public int find(int x) {
        if (p[x] != x) {
            p[x] = find(p[x]);
        }
        return p[x];
    }

    public boolean union(int a, int b) {
        int pa = find(a), pb = find(b);
        if (pa == pb) {
            return false;
        }
        if (size[pa] > size[pb]) {
            p[pb] = pa;
            size[pa] += size[pb];
        } else {
            p[pa] = pb;
            size[pb] += size[pa];
        }
        return true;
    }
}

class Solution {
    public int countComponents(int n, int[][] edges) {
        UnionFind uf = new UnionFind(n);
        for (var e : edges) {
            n -= uf.union(e[0], e[1]) ? 1 : 0;
        }
        return n;
    }
}
class UnionFind {
public:
    UnionFind(int n) {
        p = vector<int>(n);
        size = vector<int>(n, 1);
        iota(p.begin(), p.end(), 0);
    }

    bool unite(int a, int b) {
        int pa = find(a), pb = find(b);
        if (pa == pb) {
            return false;
        }
        if (size[pa] > size[pb]) {
            p[pb] = pa;
            size[pa] += size[pb];
        } else {
            p[pa] = pb;
            size[pb] += size[pa];
        }
        return true;
    }

    int find(int x) {
        if (p[x] != x) {
            p[x] = find(p[x]);
        }
        return p[x];
    }

private:
    vector<int> p, size;
};

class Solution {
public:
    int countComponents(int n, vector<vector<int>>& edges) {
        UnionFind uf(n);
        for (auto& e : edges) {
            n -= uf.unite(e[0], e[1]);
        }
        return n;
    }
};
class UnionFind {
    p: number[];
    size: number[];
    constructor(n: number) {
        this.p = Array(n)
            .fill(0)
            .map((_, i) => i);
        this.size = Array(n).fill(1);
    }

    find(x: number): number {
        if (this.p[x] !== x) {
            this.p[x] = this.find(this.p[x]);
        }
        return this.p[x];
    }

    union(a: number, b: number): boolean {
        const [pa, pb] = [this.find(a), this.find(b)];
        if (pa === pb) {
            return false;
        }
        if (this.size[pa] > this.size[pb]) {
            this.p[pb] = pa;
            this.size[pa] += this.size[pb];
        } else {
            this.p[pa] = pb;
            this.size[pb] += this.size[pa];
        }
        return true;
    }
}

function countComponents(n: number, edges: number[][]): number {
    const uf = new UnionFind(n);
    for (const [a, b] of edges) {
        n -= uf.union(a, b) ? 1 : 0;
    }
    return n;
}
type unionFind struct {
	p, size []int
}

func newUnionFind(n int) *unionFind {
	p := make([]int, n)
	size := make([]int, n)
	for i := range p {
		p[i] = i
		size[i] = 1
	}
	return &unionFind{p, size}
}

func (uf *unionFind) find(x int) int {
	if uf.p[x] != x {
		uf.p[x] = uf.find(uf.p[x])
	}
	return uf.p[x]
}

func (uf *unionFind) union(a, b int) bool {
	pa, pb := uf.find(a), uf.find(b)
	if pa == pb {
		return false
	}
	if uf.size[pa] > uf.size[pb] {
		uf.p[pb] = pa
		uf.size[pa] += uf.size[pb]
	} else {
		uf.p[pa] = pb
		uf.size[pb] += uf.size[pa]
	}
	return true
}

func countComponents(n int, edges [][]int) int {
	uf := newUnionFind(n)
	for _, e := range edges {
		if uf.union(e[0], e[1]) {
			n--
		}
	}
	return n
}
Try this approach in the editor

Approach 3: BFS

We can also use BFS (Breadth-First Search) to count the number of connected components in the graph.

Similar to Solution 1, we first construct an adjacency list g based on the given edges. Then we traverse all nodes. For each node, if it has not been visited, we start BFS traversal from this node, marking all its adjacent nodes as visited, until all its adjacent nodes have been visited. In this way, we have found a connected component, and the answer is incremented by one.

After traversing all nodes, we get the number of connected components in the graph.

The time complexity is O(n + m), and the space complexity is O(n + m). Where n and m are the number of nodes and edges, respectively.

class Solution:
    def countComponents(self, n: int, edges: List[List[int]]) -> int:
        g = [[] for _ in range(n)]
        for a, b in edges:
            g[a].append(b)
            g[b].append(a)
        vis = set()
        ans = 0
        for i in range(n):
            if i in vis:
                continue
            vis.add(i)
            q = deque([i])
            while q:
                a = q.popleft()
                for b in g[a]:
                    if b not in vis:
                        vis.add(b)
                        q.append(b)
            ans += 1
        return ans
class Solution {
    public int countComponents(int n, int[][] edges) {
        List<Integer>[] g = new List[n];
        Arrays.setAll(g, k -> new ArrayList<>());
        for (var e : edges) {
            int a = e[0], b = e[1];
            g[a].add(b);
            g[b].add(a);
        }
        int ans = 0;
        boolean[] vis = new boolean[n];
        for (int i = 0; i < n; ++i) {
            if (vis[i]) {
                continue;
            }
            vis[i] = true;
            ++ans;
            Deque<Integer> q = new ArrayDeque<>();
            q.offer(i);
            while (!q.isEmpty()) {
                int a = q.poll();
                for (int b : g[a]) {
                    if (!vis[b]) {
                        vis[b] = true;
                        q.offer(b);
                    }
                }
            }
        }
        return ans;
    }
}
class Solution {
public:
    int countComponents(int n, vector<vector<int>>& edges) {
        vector<int> g[n];
        for (auto& e : edges) {
            int a = e[0], b = e[1];
            g[a].push_back(b);
            g[b].push_back(a);
        }
        vector<bool> vis(n);
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            if (vis[i]) {
                continue;
            }
            vis[i] = true;
            ++ans;
            queue<int> q{{i}};
            while (!q.empty()) {
                int a = q.front();
                q.pop();
                for (int b : g[a]) {
                    if (!vis[b]) {
                        vis[b] = true;
                        q.push(b);
                    }
                }
            }
        }
        return ans;
    }
};
function countComponents(n: number, edges: number[][]): number {
    const g: Map<number, number[]> = new Map(Array.from({ length: n }, (_, i) => [i, []]));
    for (const [a, b] of edges) {
        g.get(a)!.push(b);
        g.get(b)!.push(a);
    }

    const vis = new Set<number>();
    let ans = 0;
    for (const [i] of g) {
        if (vis.has(i)) {
            continue;
        }
        const q = [i];
        for (const j of q) {
            if (vis.has(j)) {
                continue;
            }
            vis.add(j);
            q.push(...g.get(j)!);
        }
        ans++;
    }
    return ans;
}
func countComponents(n int, edges [][]int) (ans int) {
	g := make([][]int, n)
	for _, e := range edges {
		a, b := e[0], e[1]
		g[a] = append(g[a], b)
		g[b] = append(g[b], a)
	}
	vis := make([]bool, n)
	for i := range g {
		if vis[i] {
			continue
		}
		vis[i] = true
		ans++
		q := []int{i}
		for len(q) > 0 {
			a := q[0]
			q = q[1:]
			for _, b := range g[a] {
				if !vis[b] {
					vis[b] = true
					q = append(q, b)
				}
			}
		}
	}
	return
}
Try this approach in the editor

Complexity comparison

ApproachComplexity
DFS–
Union-Find–
BFS–

Complexity analysis

ApproachTimeSpaceWhen to Use
DFS/BFS Graph TraversalO(n + m)O(n + m)General graph problems when adjacency lists are easy to build and you want explicit traversal
Union-Find (Disjoint Set)O(n + m · α(n))O(n)Connectivity problems, dynamic edge additions, or when avoiding adjacency lists

Frequently asked questions

Is Number of Connected Components in an Undirected Graph easy or hard?

The problem is rated Medium because it requires understanding graph representation and traversal. The algorithm itself is straightforward once you know DFS/BFS or Union-Find, but recognizing that each traversal corresponds to one connected component is the key insight.

Number of Connected Components in an Undirected Graph Python/Java solution

The Python and Java solutions typically build an adjacency list using lists or hash maps and run DFS to mark reachable nodes. Each unvisited node starts a new traversal, incrementing the component count. The same logic translates easily across Python, Java, C++, Go, and JavaScript with O(n + m) complexity.

How to solve Number of Connected Components in an Undirected Graph in O(n)?

Strict O(n) is only possible when the number of edges is proportional to nodes. In general graphs the correct bound is O(n + m). Build an adjacency list and run DFS or BFS from each unvisited node, marking reachable nodes. The number of times you start a new traversal equals the number of connected components.

What is the best approach for Number of Connected Components in an Undirected Graph?

Depth-First Search (DFS) or Breadth-First Search (BFS) traversal is the most straightforward approach. Build an adjacency list and start a traversal from every unvisited node, counting each traversal as a new component. This runs in O(n + m) time where n is the number of nodes and m is the number of edges. Union-Find is another strong alternative, especially for dynamic connectivity problems.

Is Number of Connected Components in an Undirected Graph asked at Google/Amazon/Meta?

Graph connectivity problems appear frequently in interviews at companies such as Google, Amazon, Meta, and Microsoft. This problem specifically tests graph traversal and Union-Find fundamentals, which are common building blocks in larger system and graph problems.

What data structure is used in Number of Connected Components in an Undirected Graph?

Typical solutions use an adjacency list to represent the graph along with a visited array or set for DFS/BFS traversal. Another common structure is the Union-Find (Disjoint Set Union) data structure that maintains parent pointers and ranks to efficiently merge connected nodes.

What is the time complexity of Number of Connected Components in an Undirected Graph?

The optimal complexity is O(n + m) using DFS or BFS traversal because each node and edge is visited once. Using Union-Find with path compression and union by rank gives O(n + m · α(n)), where α(n) is the inverse Ackermann function and behaves almost like a constant in practice.

Comments

Now write it yourselfClose this page and solve Number of Connected Components in an Undirected Graph in the editor, without looking.
Practise Number of Connected Components in an Undirected Graph