From 5adae0aadc0c1a9c124b49f64d0ccb5854a1c297 Mon Sep 17 00:00:00 2001 From: Atsutoshi-Honda Date: Sun, 10 May 2026 13:22:12 +0900 Subject: [PATCH] init --- 323/memo.md | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 323/memo.md diff --git a/323/memo.md b/323/memo.md new file mode 100644 index 0000000..f818c5a --- /dev/null +++ b/323/memo.md @@ -0,0 +1,188 @@ +## step1: +それぞれの集合の数を求めていくということで、Union Findを使うんだろなと思ったがUnion Findの実装方法をすっかり忘れてしまった。そこでDFSを使って各ノードを探索していくことにする。双方向グラフなのでお互いのノードに対してベクトルを辞書型で置いていき、深さ優先探索で辿っていく。edgesをfor文で回してもし辿ったことのないノードだったらそこで探索をし、そのグラフを探索済みのものとしてanswerをインクリメントしていく。 +### code +```python +from collections import defaultdict +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + x_to_neighbors = defaultdict(list) + for edge in edges: + i, j = edge + x_to_neighbors[i].append(j) + x_to_neighbors[j].append(i) + visited = set() + + def explore(x): + visited.add(neighbor) + for neighbor in x_to_neighbors[x]: + if not neighbor in visited: + explore(neighbor) + + answer = 0 + for i in range(n): + if not i in visited: + explore(i) + answer += 1 + + return answer +``` +空間計算量はO(2*E + N), 時間計算量はO(2*E + N)である。(Eはedgesのサイズ、Nはノードの数) +これでひとまずacceptされたが、一応UnionFindのやり方を調べて書いてみる。 + +```python +class UnionFind: + def __init__(self, n): + self.parent = list(range(n)) + self.rank = [1]*n + + def find(self, node): + if node == self.parent[node]: + return node + self.parent[node] = self.find(self.parent[node]) + return self.parent[node] + + def union(self, u, v): + pu = self.find(u) + pv = self.find(v) + if pu == pv: + return False + if self.rank[pu] > self.rank[pv]: + self.parent[pv] = pu + self.rank[pu] += self.rank[pv] + else: + self.parent[pu] = pv + self.rank[pv] += self.rank[pu] + return True + + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + unionFind = UnionFind(n) + for u, v in edges: + unionFind.union(u, v) + roots = set() + for i in range(n): + roots.add(unionFind.find(i)) + return len(roots) +``` +UnionFindを何も見なくても実装できるように言語化してみる。まずUnionFindの初期化ではサイズnと0からn-1までの各ノードの属する集合のルートをフィールドとしてもつ。各ノードの親は最初は各ノード自身を指定させる。findメソッドでは引数に入れたノードの集合のルートを求めて返すのだがノードAを引数にもらった際に単にself.parents[A]を求めた場合に親がノードBとして、ノードBがさらに親を持つ場合、再帰的に親の親を求めてノードが自分自身を親とする所まで遡る必要がある。またノードAの親を求める時に再びその再帰のプロセスを行うのは非効率なので、self.parent[node]を最終的な親に書き換えておく。 +unionメソッドは2つのノードを引数とし、それぞれが属している集合を合体させるのだが、もし合体させようとしてる2つのノードのルートが同じノードだった場合、それはその集合はすでに同一であることの証なので早期リターンする。もしparentが違ったら集合を合体させる。単に片方のルートのparentをもう片方のルートに設定すればいいのだが、集合のノード数が少ない方のルートの親を集合のノード数が多い方のルートに指定した方がself.parentの更新数が少なくなるのでself.rankを比較してノード数の多い方のルートを中心に結合する。この際に、結合された方のランクを結合する方へ加算するのを忘れない。self.findとself.unionの時間計算量はどちらもO(α(n))でほぼO(1)と同一視できるらしいが、もしself.findでの +`self.parent[node] = self.find(self.parent[node])` +これとunionのrank比較がないとどちらもlogNになるらしい。証明が難しそうなので軽く流しておく。 + +## step2: +### code +```python +class UnionFind: + def __init__(self, n): + self.parent = list(range(n)) + self.rank = [1]*n + + def find(self, node): + if node == self.parent[node]: + return node + self.parent[node] = self.find(self.parent[node]) + return self.parent[node] + + def union(self, u, v): + pu = self.find(u) + pv = self.find(v) + if pu == pv: + return False + if self.rank[pu] > self.rank[pv]: + self.parent[pv] = pu + self.rank[pu] += self.rank[pv] + else: + self.parent[pu] = pv + self.rank[pv] += self.rank[pu] + return True + + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + unionFind = UnionFind(n) + for u, v in edges: + unionFind.union(u, v) + roots = set() + for i in range(n): + roots.add(unionFind.find(i)) + return len(roots) +``` + +## step3: +### code +```python +class UnionFind: + def __init__(self, n: int) -> None: + self.parent = [i for i in range(n)] + self.rank = [1]*n + + def find(self, x: int) -> int: + if x == self.parent[x]: + return x + self.parent[x] = self.find(self.parent[x]) + return self.parent[x] + + def union(self, u: int, v: int) -> bool: + pu, pv = self.find(u), self.find(v) + if pu == pv: + return False + if self.rank[pu] >= self.rank[pv]: + self.parent[pv] = pu + self.rank[pu] += self.rank[pv] + else: + self.parent[pu] = pv + self.rank[pv] += pu + return True + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + unionFind = UnionFind(n) + for edge in edges: + u, v = edge + unionFind.union(u, v) + roots = set() + for i in range(n): + roots.add(unionFind.find(i)) + return len(roots) +``` + +## 他の人のコードを読んでみる +https://github.com/h1rosaka/arai60/pull/60/changes +ブルートフォースで実装されていて、コメントにあったこの書き方がとても単純で分かりやすいと感じた。だが時間計算量でO(E*N)かかってしまう。(Eはエッジの数、Nはノードの数) +```python +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + connected_sets = [] + unseen_nums = set([num for num in range(n)]) + def serach_in_connected_sets(target:int) -> int: + for i in range(len(connected_sets)): + if target in connected_sets[i]: + return i + return -1 + + for x, y in edges: + unseen_nums.discard(x) + unseen_nums.discard(y) + + set_num_x = serach_in_connected_sets(x) + set_num_y = serach_in_connected_sets(y) + + if set_num_x == -1 and set_num_y == -1: + connected_sets.append({x, y}) + continue + + if set_num_x != -1 and set_num_y == -1: + connected_sets[set_num_x].add(y) + continue + + if set_num_x == -1 and set_num_y != -1: + connected_sets[set_num_y].add(x) + continue + + if set_num_x != set_num_y: + connected_sets[set_num_x] |= connected_sets[set_num_y] + connected_sets.pop(set_num_y) + + return len(connected_sets) + len(unseen_nums) +```