From 9ba75affa69e7b61ca960fd7ced6ebba03074586 Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Mon, 9 Mar 2026 17:56:24 +0900 Subject: [PATCH 1/5] step12 --- memo.md | 253 ++++++++++++++++++++++++++++++++++++++++++ step1-1_union_find.py | 48 ++++++++ step1-2_dfs.py | 36 ++++++ step2-1_dfs.py | 33 ++++++ step2-2_union_find.py | 48 ++++++++ 5 files changed, 418 insertions(+) create mode 100644 step1-1_union_find.py create mode 100644 step1-2_dfs.py create mode 100644 step2-1_dfs.py create mode 100644 step2-2_union_find.py diff --git a/memo.md b/memo.md index 4bd0397..df48a65 100644 --- a/memo.md +++ b/memo.md @@ -1 +1,254 @@ # Step1 + +## アプローチ + +* ノードとエッジが与えられて, 繋がっている塊の数を返せばいい + * グラフ理論で言うところの, 森?かな +* Union-Findを使って, エッジごとに更新をしていけば, 各ノードのグループを表す親ノードを得られる + * エッジの個数 E + * ノードの個数 N + * 1回のUnionにかかる最悪計算量 + * O(logN)かな. 逆アッカーマンとかあったけど, あまり詳しく覚えていない. + * 最後に, すべてのノードをみて, 親番号の種類を確認する O(N) + * トータルで, O(ElogN + N)? + * step数としては, 5 * 10^3 * 10 + 2 * 10^3 = 10^4くらい + * 実行時間は, 10^7 step / secで見積もると, 0.001秒くらいか +* DFSやBFSを使って全部見ることもできそう + * 最初のノードを適当にとって辿る + * 辿り切ったら, まだ見ていないとこから一個適当にとって同様の手順を繰り返す + * 全てのノードを見るので, O(N) + * だけど, まだ見ていないところから一個取るのがO(1)でできる?? + * visitedとかを使えば結局訪れるのはたかだか1回だからO(N)で良さそう + + +## Code1-1 (Union Find) + +```python +from typing import List + + +class UnionFind: + def __init__(self, size): + self.parents = [i for i in range(size)] + self.rank = [0] * size + + def find(self, idx): + if self.parents[idx] != idx: + self.parents[idx] = self.find(self.parents[idx]) + return self.parents[idx] + + def union(self, idx1, idx2): + parent1 = self.find(idx1) + parent2 = self.find(idx2) + if parent1 == parent2: + return + + if self.rank[parent1] < self.rank[parent2]: + self.parents[parent1] = parent2 + return + elif self.rank[parent2] < self.rank[parent1]: + self.parents[parent2] = parent1 + return + else: + self.parents[parent2] = parent1 + self.rank[parent1] += 1 + return + + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + uf = UnionFind(n) + for edge in edges: + uf.union(edge[0], edge[1]) + + num_components = 0 + seen_parents = set() + for i in range(n): + parent = uf.find(i) + if parent in seen_parents: + continue + seen_parents.add(parent) + num_components += 1 + + return num_components + +``` + +## Code1-2 (DFS) + +* 繋がっているノードを確認するのにO(N)かかるから計算量の見積もりがアプローチで考えたものと違くなりそう +* 各ノードに対して, + * 繋がっているノードを確認 O(N) +* O(N^2)かな +* でも, 繋がっているノードをdictとして持つようにしたら, 繋がっているノードの確認はO(1)でよくなる + * ただ, 結局すべてのノードが辺で結ばれていたら, 繋がっているノードはN - 1個になるのでO(N^2)は免れないか?? +* いや, 今回の場合は, 一度訪れられたノードに再び行くことはないから, O(N) +* エッジの前処理も含めたらO(N + E) + +```python +from typing import List + +class Solution: + + def countComponents(self, n: int, edges: List[List[int]]) -> int: + + def create_adjacent_matrix(): + adjacent_matrix = [[False] * n for _ in range(n)] + for edge in edges: + node1 = edge[0] + node2 = edge[1] + adjacent_matrix[node1][node2] = True + adjacent_matrix[node2][node1] = True + return adjacent_matrix + + def visit_all_connected_nodes(cur_node, adj_matrix, visited): + if visited[cur_node]: + return + visited[cur_node] = True + for next_node in range(n): + if cur_node == next_node: + continue + if not adj_matrix[cur_node][next_node]: + continue + visit_all_connected_nodes(next_node, adj_matrix, visited) + return + + visited = [False] * n + adj_matrix = create_adjacent_matrix() + num_components = 0 + for i in range(n): + if visited[i]: + continue + num_components += 1 + visit_all_connected_nodes(i, adj_matrix, visited) + return num_components + +``` + +# 他の人のコードを確認 + +* 1 - https://github.com/mamo3gr/arai60/pull/56 + * 言語: Python + * DFSとUnionFindの両方を実装 + * DFS: DFSでスタックを使用し, 辺は隣接行列ではなくて辞書として用意 + * UnionFind: 実装はほぼ自分のものと一致. 違いとしては`rank`じゃなくて`size`を使用している +* 2 - https://github.com/Hiroto-Iizuka/coding_practice/pull/19 + * 言語: Python + * 1とほぼ同様 +* 3 - https://github.com/TakayaShirai/leetcode_practice/pull/19 + * 言語: Swift + * 手作業の様子を考えているのがいいと思った + * 実行時間が図られているのも良い + * 1, 2と同様 +* 4 - https://github.com/xbam326/leetcode/pull/21 + * 言語: Python + * 1, 2, 3と同様 +* 5 - https://github.com/dxxsxsxkx/leetcode/pull/19 + * 言語: C++ + * 辞書ではなく, 二次元配列で辺のつながりを表現. adjacency_list[i] = (nodeiにつながるノードのリスト) + * あとは1,2,3,4と同様 + +# 調べたこと + +* UnionFindの計算量 + * 最適化なしだったら, O(N) + * rankやsizeによって木の高さを制限したら, O(logN) + * Path Compression + rankにしたら, O(α(N))で, これはほぼ定数時間と見ていい + + +# Step2 + +## Code2-1 (DFS) + +* 再帰をスタックで行うようにした +* 辞書として辺を登録した + +```python +from typing import List +from collections import defaultdict + +class Solution: + + def countComponents(self, n: int, edges: List[List[int]]) -> int: + + node_to_neighbors = defaultdict(set) + for edge in edges: + node_to_neighbors[edge[0]].add(edge[1]) + node_to_neighbors[edge[1]].add(edge[0]) + + visited = [False] * n + + def visit_connected_nodes(init_node): + frontier = [init_node] + while frontier: + node = frontier.pop() + if visited[node]: + continue + visited[node] = True + for neighbor_node in node_to_neighbors[node]: + if not visited[neighbor_node]: + frontier.append(neighbor_node) + return + + num_components = 0 + for i in range(n): + if visited[i]: + continue + num_components += 1 + visit_connected_nodes(i) + return num_components + +``` + +## Code2-2 (Union Find) + +```python +from typing import List + + +class UnionFind: + def __init__(self, size): + self.parents = [i for i in range(size)] + self.rank = [0] * size + + def find(self, idx): + if self.parents[idx] != idx: + self.parents[idx] = self.find(self.parents[idx]) + return self.parents[idx] + + def union(self, idx1, idx2): + parent1 = self.find(idx1) + parent2 = self.find(idx2) + if parent1 == parent2: + return + + if self.rank[parent1] < self.rank[parent2]: + self.parents[parent1] = parent2 + return + elif self.rank[parent2] < self.rank[parent1]: + self.parents[parent2] = parent1 + return + else: + self.parents[parent2] = parent1 + self.rank[parent1] += 1 + return + + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + uf = UnionFind(n) + for edge in edges: + uf.union(edge[0], edge[1]) + + num_components = 0 + seen_parents = set() + for i in range(n): + parent = uf.find(i) + if parent in seen_parents: + continue + seen_parents.add(parent) + num_components += 1 + + return num_components + +``` \ No newline at end of file diff --git a/step1-1_union_find.py b/step1-1_union_find.py new file mode 100644 index 0000000..c71c95f --- /dev/null +++ b/step1-1_union_find.py @@ -0,0 +1,48 @@ +from typing import List + + +class UnionFind: + def __init__(self, size): + self.parents = [i for i in range(size)] + self.rank = [0] * size + + def find(self, idx): + if self.parents[idx] != idx: + self.parents[idx] = self.find(self.parents[idx]) + return self.parents[idx] + + def union(self, idx1, idx2): + parent1 = self.find(idx1) + parent2 = self.find(idx2) + if parent1 == parent2: + return + + if self.rank[parent1] < self.rank[parent2]: + self.parents[parent1] = parent2 + return + elif self.rank[parent2] < self.rank[parent1]: + self.parents[parent2] = parent1 + return + else: + self.parents[parent2] = parent1 + self.rank[parent1] += 1 + return + + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + uf = UnionFind(n) + for edge in edges: + uf.union(edge[0], edge[1]) + + num_components = 0 + seen_parents = set() + for i in range(n): + parent = uf.find(i) + if parent in seen_parents: + continue + seen_parents.add(parent) + num_components += 1 + + return num_components + \ No newline at end of file diff --git a/step1-2_dfs.py b/step1-2_dfs.py new file mode 100644 index 0000000..852eb31 --- /dev/null +++ b/step1-2_dfs.py @@ -0,0 +1,36 @@ +from typing import List + +class Solution: + + def countComponents(self, n: int, edges: List[List[int]]) -> int: + + def create_adjacent_matrix(): + adjacent_matrix = [[False] * n for _ in range(n)] + for edge in edges: + node1 = edge[0] + node2 = edge[1] + adjacent_matrix[node1][node2] = True + adjacent_matrix[node2][node1] = True + return adjacent_matrix + + def visit_all_connected_nodes(cur_node, adj_matrix, visited): + if visited[cur_node]: + return + visited[cur_node] = True + for next_node in range(n): + if cur_node == next_node: + continue + if not adj_matrix[cur_node][next_node]: + continue + visit_all_connected_nodes(next_node, adj_matrix, visited) + return + + visited = [False] * n + adj_matrix = create_adjacent_matrix() + num_components = 0 + for i in range(n): + if visited[i]: + continue + num_components += 1 + visit_all_connected_nodes(i, adj_matrix, visited) + return num_components \ No newline at end of file diff --git a/step2-1_dfs.py b/step2-1_dfs.py new file mode 100644 index 0000000..063cc63 --- /dev/null +++ b/step2-1_dfs.py @@ -0,0 +1,33 @@ +from typing import List +from collections import defaultdict + +class Solution: + + def countComponents(self, n: int, edges: List[List[int]]) -> int: + + node_to_neighbors = defaultdict(set) + for edge in edges: + node_to_neighbors[edge[0]].add(edge[1]) + node_to_neighbors[edge[1]].add(edge[0]) + + visited = [False] * n + + def visit_connected_nodes(init_node): + frontier = [init_node] + while frontier: + node = frontier.pop() + if visited[node]: + continue + visited[node] = True + for neighbor_node in node_to_neighbors[node]: + if not visited[neighbor_node]: + frontier.append(neighbor_node) + return + + num_components = 0 + for i in range(n): + if visited[i]: + continue + num_components += 1 + visit_connected_nodes(i) + return num_components \ No newline at end of file diff --git a/step2-2_union_find.py b/step2-2_union_find.py new file mode 100644 index 0000000..c71c95f --- /dev/null +++ b/step2-2_union_find.py @@ -0,0 +1,48 @@ +from typing import List + + +class UnionFind: + def __init__(self, size): + self.parents = [i for i in range(size)] + self.rank = [0] * size + + def find(self, idx): + if self.parents[idx] != idx: + self.parents[idx] = self.find(self.parents[idx]) + return self.parents[idx] + + def union(self, idx1, idx2): + parent1 = self.find(idx1) + parent2 = self.find(idx2) + if parent1 == parent2: + return + + if self.rank[parent1] < self.rank[parent2]: + self.parents[parent1] = parent2 + return + elif self.rank[parent2] < self.rank[parent1]: + self.parents[parent2] = parent1 + return + else: + self.parents[parent2] = parent1 + self.rank[parent1] += 1 + return + + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + uf = UnionFind(n) + for edge in edges: + uf.union(edge[0], edge[1]) + + num_components = 0 + seen_parents = set() + for i in range(n): + parent = uf.find(i) + if parent in seen_parents: + continue + seen_parents.add(parent) + num_components += 1 + + return num_components + \ No newline at end of file From ee986ed822eb108093b41edcba5d1ce241123234 Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Mon, 9 Mar 2026 18:16:27 +0900 Subject: [PATCH 2/5] =?UTF-8?q?step3=201=E5=9B=9E=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- memo.md | 105 +++++++++--------- step1-2_dfs.py => step1-1_dfs.py | 0 ...1-1_union_find.py => step1-2_union_find.py | 0 step3-1_dfs.py | 36 ++++++ step3-2_union_find.py | 43 +++++++ 5 files changed, 132 insertions(+), 52 deletions(-) rename step1-2_dfs.py => step1-1_dfs.py (100%) rename step1-1_union_find.py => step1-2_union_find.py (100%) create mode 100644 step3-1_dfs.py create mode 100644 step3-2_union_find.py diff --git a/memo.md b/memo.md index df48a65..a32e659 100644 --- a/memo.md +++ b/memo.md @@ -21,7 +21,59 @@ * visitedとかを使えば結局訪れるのはたかだか1回だからO(N)で良さそう -## Code1-1 (Union Find) + +## Code1-1 (DFS) + +* 繋がっているノードを確認するのにO(N)かかるから計算量の見積もりがアプローチで考えたものと違くなりそう +* 各ノードに対して, + * 繋がっているノードを確認 O(N) +* O(N^2)かな +* でも, 繋がっているノードをdictとして持つようにしたら, 繋がっているノードの確認はO(1)でよくなる + * ただ, 結局すべてのノードが辺で結ばれていたら, 繋がっているノードはN - 1個になるのでO(N^2)は免れないか?? +* いや, 今回の場合は, 一度訪れられたノードに再び行くことはないから, O(N) +* エッジの前処理も含めたらO(N + E) + +```python +from typing import List + +class Solution: + + def countComponents(self, n: int, edges: List[List[int]]) -> int: + + def create_adjacent_matrix(): + adjacent_matrix = [[False] * n for _ in range(n)] + for edge in edges: + node1 = edge[0] + node2 = edge[1] + adjacent_matrix[node1][node2] = True + adjacent_matrix[node2][node1] = True + return adjacent_matrix + + def visit_all_connected_nodes(cur_node, adj_matrix, visited): + if visited[cur_node]: + return + visited[cur_node] = True + for next_node in range(n): + if cur_node == next_node: + continue + if not adj_matrix[cur_node][next_node]: + continue + visit_all_connected_nodes(next_node, adj_matrix, visited) + return + + visited = [False] * n + adj_matrix = create_adjacent_matrix() + num_components = 0 + for i in range(n): + if visited[i]: + continue + num_components += 1 + visit_all_connected_nodes(i, adj_matrix, visited) + return num_components + +``` + +## Code1-2 (Union Find) ```python from typing import List @@ -74,57 +126,6 @@ class Solution: ``` -## Code1-2 (DFS) - -* 繋がっているノードを確認するのにO(N)かかるから計算量の見積もりがアプローチで考えたものと違くなりそう -* 各ノードに対して, - * 繋がっているノードを確認 O(N) -* O(N^2)かな -* でも, 繋がっているノードをdictとして持つようにしたら, 繋がっているノードの確認はO(1)でよくなる - * ただ, 結局すべてのノードが辺で結ばれていたら, 繋がっているノードはN - 1個になるのでO(N^2)は免れないか?? -* いや, 今回の場合は, 一度訪れられたノードに再び行くことはないから, O(N) -* エッジの前処理も含めたらO(N + E) - -```python -from typing import List - -class Solution: - - def countComponents(self, n: int, edges: List[List[int]]) -> int: - - def create_adjacent_matrix(): - adjacent_matrix = [[False] * n for _ in range(n)] - for edge in edges: - node1 = edge[0] - node2 = edge[1] - adjacent_matrix[node1][node2] = True - adjacent_matrix[node2][node1] = True - return adjacent_matrix - - def visit_all_connected_nodes(cur_node, adj_matrix, visited): - if visited[cur_node]: - return - visited[cur_node] = True - for next_node in range(n): - if cur_node == next_node: - continue - if not adj_matrix[cur_node][next_node]: - continue - visit_all_connected_nodes(next_node, adj_matrix, visited) - return - - visited = [False] * n - adj_matrix = create_adjacent_matrix() - num_components = 0 - for i in range(n): - if visited[i]: - continue - num_components += 1 - visit_all_connected_nodes(i, adj_matrix, visited) - return num_components - -``` - # 他の人のコードを確認 * 1 - https://github.com/mamo3gr/arai60/pull/56 diff --git a/step1-2_dfs.py b/step1-1_dfs.py similarity index 100% rename from step1-2_dfs.py rename to step1-1_dfs.py diff --git a/step1-1_union_find.py b/step1-2_union_find.py similarity index 100% rename from step1-1_union_find.py rename to step1-2_union_find.py diff --git a/step3-1_dfs.py b/step3-1_dfs.py new file mode 100644 index 0000000..56d61cf --- /dev/null +++ b/step3-1_dfs.py @@ -0,0 +1,36 @@ +from typing import List +from collections import defaultdict + + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + + node_to_neighbors = defaultdict(set) + for edge in edges: + node_to_neighbors[edge[0]].add(edge[1]) + node_to_neighbors[edge[1]].add(edge[0]) + + visited = [False] * n + + def visit_connected(init_node): + if visited[init_node]: + return + frontier = [init_node] + while frontier: + node = frontier.pop() + if visited[node]: + continue + visited[node] = True + for neighbor_node in node_to_neighbors[node]: + if not visited[neighbor_node]: + frontier.append(neighbor_node) + return + + num_components = 0 + for node in range(n): + if visited[node]: + continue + num_components += 1 + visit_connected(node) + return num_components + \ No newline at end of file diff --git a/step3-2_union_find.py b/step3-2_union_find.py new file mode 100644 index 0000000..b1e598f --- /dev/null +++ b/step3-2_union_find.py @@ -0,0 +1,43 @@ +from typing import List + +class UnionFind: + def __init__(self, size): + self.parents = [i for i in range(size)] + self.rank = [0] * size + + def find(self, idx): + if self.parents[idx] != idx: + self.parents[idx] = self.find(self.parents[idx]) + return self.parents[idx] + + def union(self, idx1, idx2): + parent1 = self.find(idx1) + parent2 = self.find(idx2) + if parent1 == parent2: + return + if self.rank[parent1] < self.rank[parent2]: + self.parents[parent1] = parent2 + return + if self.rank[parent2] < self.rank[parent1]: + self.parents[parent2] = parent1 + return + self.parents[parent2] = parent1 + self.rank[parent1] += 1 + return + + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + uf = UnionFind(n) + for edge in edges: + uf.union(edge[0], edge[1]) + + seen_parent = set() + num_components = 0 + for node in range(n): + parent = uf.find(node) + if parent in seen_parent: + continue + seen_parent.add(parent) + num_components += 1 + return num_components From 63d8284f140dd1b34dc1418b62ee859a9b11f12c Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Mon, 9 Mar 2026 18:29:41 +0900 Subject: [PATCH 3/5] =?UTF-8?q?step3=20=EF=BC=92=E5=9B=9E=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- step3-1_dfs.py | 8 ++++---- step3-2_union_find.py | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/step3-1_dfs.py b/step3-1_dfs.py index 56d61cf..0630a19 100644 --- a/step3-1_dfs.py +++ b/step3-1_dfs.py @@ -22,9 +22,9 @@ def visit_connected(init_node): continue visited[node] = True for neighbor_node in node_to_neighbors[node]: - if not visited[neighbor_node]: - frontier.append(neighbor_node) - return + if visited[neighbor_node]: + continue + frontier.append(neighbor_node) num_components = 0 for node in range(n): @@ -32,5 +32,5 @@ def visit_connected(init_node): continue num_components += 1 visit_connected(node) + return num_components - \ No newline at end of file diff --git a/step3-2_union_find.py b/step3-2_union_find.py index b1e598f..7fb0f58 100644 --- a/step3-2_union_find.py +++ b/step3-2_union_find.py @@ -1,5 +1,6 @@ from typing import List + class UnionFind: def __init__(self, size): self.parents = [i for i in range(size)] @@ -24,20 +25,21 @@ def union(self, idx1, idx2): self.parents[parent2] = parent1 self.rank[parent1] += 1 return - class Solution: def countComponents(self, n: int, edges: List[List[int]]) -> int: uf = UnionFind(n) - for edge in edges: + for edge in edges: uf.union(edge[0], edge[1]) - seen_parent = set() num_components = 0 + + seen_parent = set() for node in range(n): parent = uf.find(node) if parent in seen_parent: continue seen_parent.add(parent) num_components += 1 - return num_components + + return num_components \ No newline at end of file From 3e5a0bfbfea0b4db0da7f70624632f1f85e8383b Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Mon, 9 Mar 2026 18:35:17 +0900 Subject: [PATCH 4/5] =?UTF-8?q?step3=203=E5=9B=9E=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- step3-1_dfs.py | 2 +- step3-2_union_find.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/step3-1_dfs.py b/step3-1_dfs.py index 0630a19..d3a5612 100644 --- a/step3-1_dfs.py +++ b/step3-1_dfs.py @@ -25,6 +25,7 @@ def visit_connected(init_node): if visited[neighbor_node]: continue frontier.append(neighbor_node) + return num_components = 0 for node in range(n): @@ -32,5 +33,4 @@ def visit_connected(init_node): continue num_components += 1 visit_connected(node) - return num_components diff --git a/step3-2_union_find.py b/step3-2_union_find.py index 7fb0f58..f73d18b 100644 --- a/step3-2_union_find.py +++ b/step3-2_union_find.py @@ -1,6 +1,5 @@ from typing import List - class UnionFind: def __init__(self, size): self.parents = [i for i in range(size)] @@ -19,7 +18,7 @@ def union(self, idx1, idx2): if self.rank[parent1] < self.rank[parent2]: self.parents[parent1] = parent2 return - if self.rank[parent2] < self.rank[parent1]: + if self.rank[parent2] < self.rank[parent2]: self.parents[parent2] = parent1 return self.parents[parent2] = parent1 @@ -31,9 +30,8 @@ def countComponents(self, n: int, edges: List[List[int]]) -> int: uf = UnionFind(n) for edge in edges: uf.union(edge[0], edge[1]) - - num_components = 0 + num_components = 0 seen_parent = set() for node in range(n): parent = uf.find(node) @@ -42,4 +40,4 @@ def countComponents(self, n: int, edges: List[List[int]]) -> int: seen_parent.add(parent) num_components += 1 - return num_components \ No newline at end of file + return num_components From 3267cd22d2195493fa93c48ce279019ef2d36c6a Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Mon, 9 Mar 2026 18:36:08 +0900 Subject: [PATCH 5/5] memo --- memo.md | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/memo.md b/memo.md index a32e659..546b61c 100644 --- a/memo.md +++ b/memo.md @@ -252,4 +252,98 @@ class Solution: return num_components -``` \ No newline at end of file +``` + +# Step3 + +## Code3-1 (DFS) + +```python +from typing import List +from collections import defaultdict + + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + + node_to_neighbors = defaultdict(set) + for edge in edges: + node_to_neighbors[edge[0]].add(edge[1]) + node_to_neighbors[edge[1]].add(edge[0]) + + visited = [False] * n + + def visit_connected(init_node): + if visited[init_node]: + return + frontier = [init_node] + while frontier: + node = frontier.pop() + if visited[node]: + continue + visited[node] = True + for neighbor_node in node_to_neighbors[node]: + if visited[neighbor_node]: + continue + frontier.append(neighbor_node) + return + + num_components = 0 + for node in range(n): + if visited[node]: + continue + num_components += 1 + visit_connected(node) + return num_components + +``` + +## Code3-2 (Union Find) + +```python +from typing import List + +class UnionFind: + def __init__(self, size): + self.parents = [i for i in range(size)] + self.rank = [0] * size + + def find(self, idx): + if self.parents[idx] != idx: + self.parents[idx] = self.find(self.parents[idx]) + return self.parents[idx] + + def union(self, idx1, idx2): + parent1 = self.find(idx1) + parent2 = self.find(idx2) + if parent1 == parent2: + return + if self.rank[parent1] < self.rank[parent2]: + self.parents[parent1] = parent2 + return + if self.rank[parent2] < self.rank[parent2]: + self.parents[parent2] = parent1 + return + self.parents[parent2] = parent1 + self.rank[parent1] += 1 + return + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + uf = UnionFind(n) + for edge in edges: + uf.union(edge[0], edge[1]) + + num_components = 0 + seen_parent = set() + for node in range(n): + parent = uf.find(node) + if parent in seen_parent: + continue + seen_parent.add(parent) + num_components += 1 + + return num_components + + +``` \ No newline at end of file