From faeeee5130eab76d5da5a426874489a3b6d105f6 Mon Sep 17 00:00:00 2001 From: Masakuni Date: Sat, 24 Jan 2026 14:54:29 +0900 Subject: [PATCH 1/3] step1 --- .../memo.md | 24 ++++++++++++++++ .../step1.py | 28 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 problems/323.number-of-connected-components-in-an-undirected-graph/memo.md create mode 100644 problems/323.number-of-connected-components-in-an-undirected-graph/step1.py diff --git a/problems/323.number-of-connected-components-in-an-undirected-graph/memo.md b/problems/323.number-of-connected-components-in-an-undirected-graph/memo.md new file mode 100644 index 0000000..7e16fc7 --- /dev/null +++ b/problems/323.number-of-connected-components-in-an-undirected-graph/memo.md @@ -0,0 +1,24 @@ +https://neetcode.io/problems/count-connected-components/question +で代用 +## step1 +- 絵が嘘つきすぎてて問題文読み間違えているのか何回も確認する羽目になった +- 問題文全体を和訳したら流石に絵がおかしいことに自信が持てたので英語もっと自信持って読みたい +- [200]や[695]でやったようにしたら解けそう + - 全てのnodeをunvisited_nodeに入れておく + - 下記をunvisitedがなくなるまでloopさせたらOK + - unvisitedからpopして連結している部分をedgeから探していく + - 連結している部分もunvisitedから取り除く + - その際にカウントを増やす + - 最後にカウントを返す +- 20分ほどで書けた +- 辺を管理する方法はdefaultdictの他に二次元配列も選択肢にあった + - 二次元配列だと空の部分が無駄になるのであまり使いたくない気持ちだった + - 辺が多い場合はあまり変わらないが、辺が少ない場合はdefaultdictの方がサイズが空間計算量が小さくて済むと思った + - step2で確かめてみる + - 時間計算量はedgeの格納でO(m)、幅優先探索でO(n+n)だと思う(node(n)edge(m)) + - 空間計算量はedgeの格納でO(m)、幅優先探索でO(n+n)だと思う(node(n)edge(m)) + +## step2 + +## step3 + diff --git a/problems/323.number-of-connected-components-in-an-undirected-graph/step1.py b/problems/323.number-of-connected-components-in-an-undirected-graph/step1.py new file mode 100644 index 0000000..8494c59 --- /dev/null +++ b/problems/323.number-of-connected-components-in-an-undirected-graph/step1.py @@ -0,0 +1,28 @@ +# https://neetcode.io/problems/count-connected-components/question +import collections + + +class Solution: + def countComponents(self, n: int, edges: List[List[int]]) -> int: + unvisited = set(range(n)) + small_to_large_edges = collections.defaultdict(set) + for node1, node2 in edges: + small_to_large_edges[node1].add(node2) + small_to_large_edges[node2].add(node1) + + connected_component_count = 0 + + while unvisited: + node = unvisited.pop() + connected_component_count += 1 + connected_component = [node] + + while connected_component: + connected_node = connected_component.pop() + for node in small_to_large_edges[connected_node]: + if node not in unvisited: + continue + connected_component.append(node) + unvisited.remove(node) + + return connected_component_count From 79b5e62ed889661925e3575d4753a19242dac4fb Mon Sep 17 00:00:00 2001 From: Masakuni Date: Sat, 24 Jan 2026 17:50:47 +0900 Subject: [PATCH 2/3] step2 --- .../memo.md | 45 +++++++++++++++++++ .../step2.py | 36 +++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 problems/323.number-of-connected-components-in-an-undirected-graph/step2.py diff --git a/problems/323.number-of-connected-components-in-an-undirected-graph/memo.md b/problems/323.number-of-connected-components-in-an-undirected-graph/memo.md index 7e16fc7..dc4f8c6 100644 --- a/problems/323.number-of-connected-components-in-an-undirected-graph/memo.md +++ b/problems/323.number-of-connected-components-in-an-undirected-graph/memo.md @@ -19,6 +19,51 @@ https://neetcode.io/problems/count-connected-components/question - 空間計算量はedgeの格納でO(m)、幅優先探索でO(n+n)だと思う(node(n)edge(m)) ## step2 +- 一旦UnionFindでもやってみる + - 元々のnodeが小さい方を親にしていくようにする + - 管理するものがないので実装は迷わなかった + - 8分くらい +- 他の人のコードを読んでみる +- https://github.com/Yuto729/LeetCode_arai60/pull/24/files + - vertex(頂点)は思いつかなかった + - union_findでsizeの部分がなぜそれでOKになるのかがわからなかった + - Union Findの経路圧縮には3つほど方法がある + - neighberはグラフ理論ではあるノードから見て、1本の辺で到達できるノードという意味なので覚える + - adjacent_matrix(隣接行列) +- https://github.com/naoto-iwase/leetcode/pull/28 + - `listで記載する方法、勉強になりました。問題の制約から今回はありえませんが、nが大きく、疎のノードが多い場合には空間計算量の点でdictがベターな選択肢になることもあるかもしれません。`これを思ったのでdictにした + - `https://youtu.be/nclfErc9pbE?si=QP-DUn3SNRnTabkK`見てみる + - Path Compression(経路圧縮)のやり方がわかりやすかった + - Path Splitting(経路分割)と Path Halving(経路半減)はあんまり理解できなかった +- https://github.com/yas-2023/leetcode_arai60/pull/19/files +``` +num_nodesの数が決まっているので自分であればdictではなく以下のようにlistのlistで作りそうです。 +adjancy = [[] for _ in range(num_nodes)] +このように書いても使い勝手はほぼ変わらず辞書の構築コストやハッシュの計算コストなどがかからないため若干効率的かもしれません。 +ただ孤立した点が多いようなグラフだと前もって空のリストを作っているのがメモリの無駄遣いになってしまうという懸念はありそうです。また初期化に関してはdefaultdictを使うほうがシンプルで、読みやすさの面でもyasさんの実装のほうがよさそうですね。 +``` + - 辞書の構築コスト、ハッシュの計算コストと二次元配列の構築コストを比較した時の感覚が全くないのだが二次元配列の構築コストがかなり小さい? +- https://github.com/garunitule/coding_practice/pull/19/files + - 自分の実装の流れとほぼ同じだったのだが、自分は幅優先探索をしているつもりだったがよくよく考えてみると深さ優先探索になっていた + - 処理の流れとどこを優先して処理しているのか(配列の末尾には何が入っているのか)をもっとよくイメージする必要があると思った +- 他の人のコードを読むときに理解に時間が掛かりそうだと思ったらざっくり目で追って読み飛ばしてしまう癖が成長を妨げている気がする + - 次回以降は直近5人のうち理解が難しそうな2人分のコードは頭の中で処理が完全に再現できるように理解することを目指して残り3人は今まで通りざっくり読むようにして徐々に深く読めるようになっていきたい + - 今回はこの方のコードは100%理解するつもりで読む + - step1 + - 初期値-1とするのか + - parentのDictはkeyとvalueは何をあらわしてる? + - valueは親のnode,keyは現在のnode + - child_to_parentとして欲しい気持ち + - find_rootはrootと深さを返している + - unionは結合する回数なのでunion_countとかunion_numとしてもらえたらもっとわかりやすかった + - 最初union: int = 0で何するのかわからなかった + - 最後のn - unionで分かった + - これはどういう解法なのかが分かっていなくて読んだからunionが結合の回数を表しているのにすぐに思い至れなかった気もする + - step2はすっと読めた + - visitを再帰で書いており、回数が気になったがnodeが100までなのでOK(defaultは1000) + - step3もすっと読めた + - +- そういえばnodeと癖で書いてしまったがnodeは情報が少ないのでやめるように指摘されていたのを忘れていた ## step3 diff --git a/problems/323.number-of-connected-components-in-an-undirected-graph/step2.py b/problems/323.number-of-connected-components-in-an-undirected-graph/step2.py new file mode 100644 index 0000000..5f1a1b9 --- /dev/null +++ b/problems/323.number-of-connected-components-in-an-undirected-graph/step2.py @@ -0,0 +1,36 @@ +# https://neetcode.io/problems/count-connected-components/question +class UnionFind: + def __init__(self, n: int): + self.parents = list(range(n)) + + def find(self, n): + if self.parents[n] == n: + return n + return self.find(self.parents[n]) + + def union(self, node1, node2): + node1_parent = self.find(node1) + node2_parent = self.find(node2) + + if node1_parent == node2_parent: + return + + if node1_parent > node2_parent: + node1_parent, node2_parent = node2_parent, node1_parent + + self.parents[node2_parent] = node1_parent + + +class Solution: + def countComponents(self, n: int, edges: list[list[int]]) -> int: + uf = UnionFind(n) + + for edge in edges: + node1, node2 = edge + uf.union(node1, node2) + + root_nodes = set() + for i in range(n): + root_nodes.add(uf.find(i)) + + return len(root_nodes) From 1188cf59ebcce10df10af436419aaff7264a400e Mon Sep 17 00:00:00 2001 From: Masakuni Date: Sat, 24 Jan 2026 18:03:25 +0900 Subject: [PATCH 3/3] step3 --- .../memo.md | 4 +++ .../step1.py | 8 +++--- .../step3.py | 27 +++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 problems/323.number-of-connected-components-in-an-undirected-graph/step3.py diff --git a/problems/323.number-of-connected-components-in-an-undirected-graph/memo.md b/problems/323.number-of-connected-components-in-an-undirected-graph/memo.md index dc4f8c6..6a22c6d 100644 --- a/problems/323.number-of-connected-components-in-an-undirected-graph/memo.md +++ b/problems/323.number-of-connected-components-in-an-undirected-graph/memo.md @@ -17,6 +17,7 @@ https://neetcode.io/problems/count-connected-components/question - step2で確かめてみる - 時間計算量はedgeの格納でO(m)、幅優先探索でO(n+n)だと思う(node(n)edge(m)) - 空間計算量はedgeの格納でO(m)、幅優先探索でO(n+n)だと思う(node(n)edge(m)) +- small_to_large_edgesと書いていたが両側から管理するようにしたので命名が適さなくなった ## step2 - 一旦UnionFindでもやってみる @@ -66,4 +67,7 @@ adjancy = [[] for _ in range(num_nodes)] - - そういえばnodeと癖で書いてしまったがnodeは情報が少ないのでやめるように指摘されていたのを忘れていた ## step3 +- 8分ほどで書けた +- ケアレスミスもなく、1度でpassした +- edge_dict=>vertex_to_neghbersに修正した diff --git a/problems/323.number-of-connected-components-in-an-undirected-graph/step1.py b/problems/323.number-of-connected-components-in-an-undirected-graph/step1.py index 8494c59..bc0d936 100644 --- a/problems/323.number-of-connected-components-in-an-undirected-graph/step1.py +++ b/problems/323.number-of-connected-components-in-an-undirected-graph/step1.py @@ -5,10 +5,10 @@ class Solution: def countComponents(self, n: int, edges: List[List[int]]) -> int: unvisited = set(range(n)) - small_to_large_edges = collections.defaultdict(set) + edge_dict = collections.defaultdict(set) for node1, node2 in edges: - small_to_large_edges[node1].add(node2) - small_to_large_edges[node2].add(node1) + edge_dict[node1].add(node2) + edge_dict[node2].add(node1) connected_component_count = 0 @@ -19,7 +19,7 @@ def countComponents(self, n: int, edges: List[List[int]]) -> int: while connected_component: connected_node = connected_component.pop() - for node in small_to_large_edges[connected_node]: + for node in edge_dict[connected_node]: if node not in unvisited: continue connected_component.append(node) diff --git a/problems/323.number-of-connected-components-in-an-undirected-graph/step3.py b/problems/323.number-of-connected-components-in-an-undirected-graph/step3.py new file mode 100644 index 0000000..f303b2a --- /dev/null +++ b/problems/323.number-of-connected-components-in-an-undirected-graph/step3.py @@ -0,0 +1,27 @@ +# https://neetcode.io/problems/count-connected-components/question +import collections + + +class Solution: + def countComponents(self, n: int, edges: list[list[int]]) -> int: + unvisited_vertices = set(range(n)) + vertex_to_neghbers = collections.defaultdict(set) + for vertex1, vertex2 in edges: + vertex_to_neghbers[vertex1].add(vertex2) + vertex_to_neghbers[vertex2].add(vertex1) + + component_count = 0 + while unvisited_vertices: + vertex = unvisited_vertices.pop() + component_count += 1 + traversed_vertices = [vertex] + + while traversed_vertices: + vertex = traversed_vertices.pop() + for neighber in vertex_to_neghbers[vertex]: + if neighber not in unvisited_vertices: + continue + traversed_vertices.append(neighber) + unvisited_vertices.remove(neighber) + + return component_count