You are given an integer n. There is an undirected graph with n vertices, numbered from 0 to n - 1. You are given a 2D integer array edges where edges[i] = [ai, bi] denotes that there exists an undirected edge connecting vertices ai and bi.
Return the number of complete connected components of the graph.
A connected component is a subgraph of a graph in which there exists a path between any two vertices, and no vertex of the subgraph shares an edge with a vertex outside of the subgraph.
A connected component is said to be complete if there exists an edge between every pair of its vertices.
Example 1:

Input: n = 6, edges = [[0,1],[0,2],[1,2],[3,4]] Output: 3 Explanation: From the picture above, one can see that all of the components of this graph are complete.
Example 2:

Input: n = 6, edges = [[0,1],[0,2],[1,2],[3,4],[3,5]] Output: 1 Explanation: The component containing vertices 0, 1, and 2 is complete since there is an edge between every pair of two vertices. On the other hand, the component containing vertices 3, 4, and 5 is not complete since there is no edge between vertices 4 and 5. Thus, the number of complete components in this graph is 1.
Constraints:
1 <= n <= 500 <= edges.length <= n * (n - 1) / 2edges[i].length == 20 <= ai, bi <= n - 1ai != bipublic class Solution {
public int countCompleteComponents(int n, int[][] edges) {
// Initialize Union Find and edge counter
UnionFind dsu = new UnionFind(n);
Map<Integer, Integer> edgeCount = new HashMap<>();
// Connect components using edges
for (int[] edge : edges) {
dsu.union(edge[0], edge[1]);
}
// Count edges in each component
for (int[] edge : edges) {
int root = dsu.find(edge[0]);
edgeCount.put(root, edgeCount.getOrDefault(root, 0) + 1);
}
// Check if each component is complete
int completeCount = 0;
for (int vertex = 0; vertex < n; vertex++) {
if (dsu.find(vertex) == vertex) { // If vertex is root
int nodeCount = dsu.size[vertex];
int expectedEdges = (nodeCount * (nodeCount - 1)) / 2;
if (edgeCount.getOrDefault(vertex, 0) == expectedEdges) {
completeCount++;
}
}
}
return completeCount;
}
class UnionFind {
int[] parent;
int[] size; // Tracks size of each component
UnionFind(int n) {
parent = new int[n];
size = new int[n];
Arrays.fill(parent, -1);
Arrays.fill(size, 1);
}
// Find root of component with path compression
int find(int node) {
if (parent[node] == -1) {
return node;
}
return parent[node] = find(parent[node]);
}
// Union by size
void union(int node1, int node2) {
int root1 = find(node1);
int root2 = find(node2);
if (root1 == root2) {
return;
}
// Merge smaller component into larger one
if (size[root1] > size[root2]) {
parent[root2] = root1;
size[root1] += size[root2];
} else {
parent[root1] = root2;
size[root2] += size[root1];
}
}
}
}