We're given an undirected graph with n nodes (labeled 0 to n-1) and a list of edges. We need to count how many separate groups of connected nodes exist. Two nodes are in the same component if you can reach one from the other by following edges.
A component is a maximal set of nodes that are all reachable from each other. If you start at any node and follow edges as far as you can, you cover exactly one component. The answer is the number of distinct sets you would cover if you repeated that from every node.
This is a graph connectivity problem. Two families of solutions apply: traverse the graph (DFS or BFS) and count how many separate traversals it takes to reach every node, or use Union Find to merge connected nodes and count the groups that remain.
1 <= n <= 2000 -> The node count is small, so both an O(V + E) traversal and a near-linear Union Find run comfortably within limits.1 <= edges.length <= 5000 -> The graph is sparse. An adjacency list costs O(V + E) space, which is at most a few thousand entries.0 <= ai <= bi < n -> Nodes are zero-indexed and there are no self-loops, so every edge connects two distinct valid nodes.A single DFS started from a node visits every node in that node's component and nothing outside it. So we can count components by counting how many times we have to start a fresh DFS. Walk through the nodes in order. When we hit one that has not been visited yet, it belongs to a component we have not seen, so we increment the count and run DFS to mark the whole component as visited. The next unvisited node we reach must start a new component.
visited boolean array of size n.components counter to 0.components and run DFS from that node.components.Loading animation...
The traversal approach builds an adjacency list and explores the graph. The next approach takes a different route: it processes edges directly and merges groups as it goes, without ever building an adjacency list.
Union Find counts components without traversing the graph. Start by treating every node as its own component, so the count begins at n. Process the edges one by one. Each edge connects two nodes; if those nodes are in different components, merge the two components and decrement the count. If they are already in the same component, the edge changes nothing. After all edges are processed, the count is the number of components.
Union Find tracks groups with a parent pointer per node. Initially every node points to itself and is its own root. To merge two nodes, point one root at the other. Two refinements keep operations near O(1): path compression flattens the tree during a find by repointing every node on the path directly to the root, and union by rank attaches the shorter tree under the taller one so the tree stays shallow.
The structure keeps one invariant: two nodes share a root if and only if they are in the same component. Each edge either joins two distinct components (a successful union) or links two nodes already in the same component (no change). The component count starts at n and drops by exactly one per successful union, so after every edge is processed it equals the number of distinct roots, which is the number of components.
parent array where parent[i] = i (each node is its own root).rank array of zeros (for union by rank).components = n (initially, every node is a separate component).[a, b]:a and the root of b.components.components.Loading animation...