Medium
题目描述
给定一个整数 n。有一个具有 n 个顶点的无向图,顶点编号从 0 到 n - 1。给定一个二维整数数组 edges,其中 edges[i] = [ai, bi] 表示顶点 ai 和 bi 之间存在一条无向边。
返回图中完全连通分量的数量。
连通分量是图的一个子图,其中任意两个顶点之间都存在路径,并且该子图的任何顶点都不与子图外的顶点共享边。
如果连通分量的每对顶点之间都存在一条边,则称该连通分量是完全的。
示例 1:
输入:n = 6, edges = [[0,1],[0,2],[1,2],[3,4]]
输出:3
解释:从上图可以看出,该图的所有连通分量都是完全的。
示例 2:
输入:n = 6, edges = [[0,1],[0,2],[1,2],[3,4],[3,5]]
输出:1
解释:包含顶点 0、1 和 2 的分量是完全的,因为每对顶点之间都有一条边。
另一方面,包含顶点 3、4 和 5 的分量不是完全的,因为顶点 4 和 5 之间没有边。
因此,该图中完全连通分量的数量为 1。
约束条件:
- 1 <= n <= 50
- 0 <= edges.length <= n * (n - 1) / 2
- edges[i].length == 2
- 0 <= ai, bi <= n - 1
- ai != bi
- 没有重复的边
解题思路
这道题要求找到图中完全连通分量的数量。解题思路分为三个步骤:
构建图的邻接表表示:将边信息转化为便于遍历的图结构。
使用DFS/BFS找到所有连通分量:遍历所有未访问的节点,对每个未访问的节点进行深度优先搜索,找到它所属的连通分量。
判断每个连通分量是否完全:对于一个有 m 个节点的连通分量,如果它是完全图,那么它应该有 m*(m-1)/2 条边。我们可以在DFS过程中统计节点数和边数,然后验证这个条件。
核心观察:完全图的性质是每对顶点之间都有边。对于 m 个顶点的完全图,边数应该恰好是 C(m,2) = m*(m-1)/2。
算法流程:
- 构建邻接表
- 对每个未访问的节点进行DFS,统计该连通分量的节点数和边数
- 检查边数是否等于 m*(m-1)/2
- 统计满足条件的连通分量数量
推荐解法:使用DFS遍历,时间复杂度和空间复杂度都较优。
代码实现
class Solution {
public:
int countCompleteComponents(int n, vector<vector<int>>& edges) {
vector<vector<int>> graph(n);
for (auto& edge : edges) {
graph[edge[0]].push_back(edge[1]);
graph[edge[1]].push_back(edge[0]);
}
vector<bool> visited(n, false);
int completeComponents = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
int nodes = 0, edgeCount = 0;
dfs(graph, visited, i, nodes, edgeCount);
// 完全图的边数应该是 nodes * (nodes - 1) / 2
if (edgeCount == nodes * (nodes - 1) / 2) {
completeComponents++;
}
}
}
return completeComponents;
}
private:
void dfs(vector<vector<int>>& graph, vector<bool>& visited, int node, int& nodes, int& edges) {
visited[node] = true;
nodes++;
edges += graph[node].size();
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
dfs(graph, visited, neighbor, nodes, edges);
}
}
}
};
class Solution:
def countCompleteComponents(self, n: int, edges: List[List[int]]) -> int:
graph = [[] for _ in range(n)]
for a, b in edges:
graph[a].append(b)
graph[b].append(a)
visited = [False] * n
complete_components = 0
def dfs(node):
visited[node] = True
nodes = 1
edge_count = len(graph[node])
for neighbor in graph[node]:
if not visited[neighbor]:
sub_nodes, sub_edges = dfs(neighbor)
nodes += sub_nodes
edge_count += sub_edges
return nodes, edge_count
for i in range(n):
if not visited[i]:
nodes, edge_count = dfs(i)
# 每条边被计算了两次,所以除以2
if edge_count // 2 == nodes * (nodes - 1) // 2:
complete_components += 1
return complete_components
public class Solution {
public int CountCompleteComponents(int n, int[][] edges) {
List<int>[] graph = new List<int>[n];
for (int i = 0; i < n; i++) {
graph[i] = new List<int>();
}
foreach (int[] edge in edges) {
graph[edge[0]].Add(edge[1]);
graph[edge[1]].Add(edge[0]);
}
bool[] visited = new bool[n];
int completeComponents = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
int nodes = 0, edgeCount = 0;
Dfs(graph, visited, i, ref nodes, ref edgeCount);
if (edgeCount / 2 == nodes * (nodes - 1) / 2) {
completeComponents++;
}
}
}
return completeComponents;
}
private void Dfs(List<int>[] graph, bool[] visited, int node, ref int nodes, ref int edgeCount) {
visited[node] = true;
nodes++;
edgeCount += graph[node].Count;
foreach (int neighbor in graph[node]) {
if (!visited[neighbor]) {
Dfs(graph, visited, neighbor, ref nodes, ref edgeCount);
}
}
}
}
var countCompleteComponents = function(n, edges) {
const adj = Array(n).fill().map(() => []);
for (const [a, b] of edges) {
adj[a].push(b);
adj[b].push(a);
}
const visited = new Array(n).fill(false);
let completeComponents = 0;
for (let i = 0; i < n; i++) {
if (!visited[i]) {
const component = [];
dfs(i, adj, visited, component);
if (isComplete(component, adj)) {
completeComponents++;
}
}
}
return completeComponents;
};
function dfs(node, adj, visited, component) {
visited[node] = true;
component.push(node);
for (const neighbor of adj[node]) {
if (!visited[neighbor]) {
dfs(neighbor, adj, visited, component);
}
}
}
function isComplete(component, adj) {
const size = component.length;
for (const node of component) {
if (adj[node].length !== size - 1) {
return false;
}
}
return true;
}
复杂度分析
| 复杂度类型 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(V + E) | V为顶点数,E为边数,需要遍历所有顶点和边 |
| 空间复杂度 | O(V + E) | 邻接表存储图需要O(V + E)空间,DFS递归栈最坏情况需要O(V)空间 |