Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions 49-Group-Anagrams/note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 49. Group Anagrams

<https://leetcode.com/problems/group-anagrams/description/>

## step1(まず通す)

「文字を入れ替えて同じになる」という同値関係に基づいて同値類に分割するという話。

@h-masder h-masder Jun 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

正確な説明ですが、同値関係や同値類あたりはソフトウェアエンジニアの世界では普段から使う言葉とは感じないので、避けてもいいかもしれません。

「並べ替えると同じになる文字列を同じグループにまとめる」でいいかと思います。

@MA-yo-TA MA-yo-TA Jun 18, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そうですね、自分の思考過程をメモ書き程度に書いたつもりでしたが他の方も見るので言葉遣いは周りに合わせたほうが良さそうです。

↓のような説明だとだいぶマシでしょうか。
skypenguins/coding-practice#32 (comment)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

いいと思います。

効率の良さを議論するときは、それによって何が嬉しいのかも意識すると良いと思います。想像の域は出ませんが、例えば、前者の方法で実装されたプログラムが既に動いているとして、後者へ変更する価値があるのかを考えてみるとよいと思います。実行時間を見積もる意味も、そういった判断材料を得るところにあると私は思っています。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

例えば、前者の方法で実装されたプログラムが既に動いているとして、後者へ変更する価値があるのかを考えてみるとよいと思います。実行時間を見積もる意味も、そういった判断材料を得るところにあると私は思っています

まさにそうですね、困ってない(し、これからも困ることはなさそう)なら触らないというのは実務では多いですよね。

実践的なアドバイスありがとうございます!

ただし、毎回二つの文字列が同値関係を満たすか?とチェックするのは大変なので、一つの文字列を見た時に「それがどの同値類に属するか?」を判定できると嬉しい。

そのためには、それぞれの同値類に「名前」をつけたい

- 文字列じゃなくても、Counter.count() (に相当する操作)をした結果とかでもいいが、実用上は hashable であってほしい
- 代表元の取り方にルールを設けてそれをその同値類の「名前」にすると文字列で名前がついて良い
- 例えばソートする

制約上、各文字列の長さは 100 以下と短いのでソートすれば良さそう

カウントする方が線形時間で時間計算量のオーダーとしては小さくなるが、

- アルファベット小文字は 26 個しかないので長さ26のタプルを使うこともできるが、コードのわかりやすさが犠牲になりそう
- `frozenset` のように hashable な dict はないのかということが気になるが、調べたら 3.15 から `frozendict` が組み込み型に追加されるらしい。
- <https://docs.python.org/ja/3.15/library/stdtypes.html#frozendict>
- 現在の安定版の最新は 3.14 なのでまだ一般的に使える感じではなさそう

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

アルゴリズムの実行時間を見積もることをおすすめします。
Yuto729/leetcode#16 (comment)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。

今回の場合は配列長が最大 10 ^ 4 , 文字列一つ一つの長さが最大 100 なので、
(10 ^ 4 * 100 * log 100) / 10 ^ 7 ~ 0.6 s くらいという計算をして、実際と大分ずれているなという印象ですね、、(leetcode だと 20 ms くらいで終わっている)

(テストケースが最大の大きさのものがないとかかもしれないので最終的には実際に計らないとわからないのだろうと思いつつ、見積もりが雑とか計算間違ってるかもしれません。)

@h-masder h-masder Jun 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10^4 や 100 * log 100がどこからきているか書いておくと良いと思います
(前者がメモリアクセス、後者がsorted()ですね)
sorted()を使うのであれば、内部実装として、何が使われているか知っておくとよいと思います。以下のあたりを見る限りtimsortのようです。
https://github.com/python/cpython/blob/main/Objects/listsort.txt
https://github.com/python/cpython/blob/main/Objects/listobject.c#L3122

テストケースが最大の大きさのものがないとかかもしれないので最終的には実際に計らないとわからないのだろうと思いつつ

はい、テストケースでだいぶ変わると思います。ゆくゆくはleetcodeにたよらず、自分でテストケースを書く癖をつけていくとよいかと思います。(癖というより、自分でテストケースを書きたくなる気持ちを養うといったほうがいいかもしれないです)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10^4 や 100 * log 100がどこからきているか書いておくと良いと思います

省略しすぎました。サボらず伝わる書き方を心がけます。

自分でテストケースを書きたくなる気持ちを養う

ありがとうございます。まずは想定できるケースのバリエーションを増やす意識で取り組みます。

## step2(整形&他の人のコードを読む)

`sorted_string` は `sorted_` とかでもいいかも。今回は基本 string の話しかしてなくて string であることは明らかなので。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

関数名と紛らわしくなるため、同じ(もしくはほとんど似た)名前を変数名に使うのは避けたほうが良いと思います。元の sorted_string の方が意図が分かりやすいと感じました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そうですね、書いているうちにそう思うようになりました。
ありがとうございます。


あとは他の人の書き方を見る。

- <https://github.com/naoto-iwase/leetcode/pull/12#discussion_r2415269272>
- ちゃんと把握してなかったが、str だけじゃなくて string も予約語(に近い、モジュールがある)らしい
- そもそも他言語でも string は予約語なことが多いと思うので避けた方がいい
- スコープも短いし `s` くらいでもいいかも、というコメントもあった
- counter タプルに詰める時、文字コードを使う発想はなかった
- <https://github.com/shintaro1993/arai60/pull/16/changes>
- 文字ごとの出現数を数える場合は、英小文字じゃなかったらスキップしとかないとおかしくなる
- ord とか all とか、知らない組み込み関数がいろいろある

## step3(10分以内にさっとかける * 3回)
11 changes: 11 additions & 0 deletions 49-Group-Anagrams/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Solution:
def groupAnagrams(self, strs: list[str]) -> list[list[str]]:
sorted_to_originals: dict[str, list[str]] = {}
for string in strs:

@h-masder h-masder Jun 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stringは型名のように感じるので避けたいです。
(↑こちらstep2に言及されていました。失礼しました。)
wordなどはいかがでしょうか

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。word もありかなと思いつつ、"" みたいな自然言語の感覚で言うと word じゃない奴もいるよな、みたいな細かいところを気にしてました。

今考えてみると割り切って word は良い名前な気がしてます。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"" みたいな自然言語の感覚で言うと word じゃない奴もいるよな、みたいな細かいところを気にしてました。

おっしゃる通り、場合によってはword以外の変数名も考えたほうが良いかもしれないですね。

sorted_string = str(sorted(string))
if sorted_string in sorted_to_originals:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6~10行目はdefalutdictを使うこともできます。
利点は、行数が短くなる、「存在しないキーには空のリストを作り、そこへ要素を追加していく処理」が想像しやすい、あたりです。

from collections import defaultdict

class Solution:
    def groupAnagrams(self, strs: list[str]) -> list[list[str]]:
        sorted_to_originals = defaultdict(list)
        for string in strs:
            sorted_string = str(sorted(string))
            sorted_to_originals[sorted_string].append(string)

        return list(sorted_to_originals.values())

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。 defaultdict をあまり認識していなかったので調べておきます。

sorted_to_originals[sorted_string].append(string)
else:
sorted_to_originals[sorted_string] = [string]

return list(sorted_to_originals.values())
11 changes: 11 additions & 0 deletions 49-Group-Anagrams/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Solution:
def groupAnagrams(self, strs: list[str]) -> list[list[str]]:
grouped_anagrams: dict[str, list[str]] = {}
for s in strs:
sorted_s = str(sorted(s))
if sorted_s in grouped_anagrams:
grouped_anagrams[sorted_s].append(s)
else:
grouped_anagrams[sorted_s] = [s]

return list(grouped_anagrams.values())
10 changes: 10 additions & 0 deletions 49-Group-Anagrams/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Solution:
def groupAnagrams(self, strs: list[str]) -> list[list[str]]:
grouped_anagrams: dict[str, list[str]] = {}
for s in strs:
sorted_s = str(sorted(s))
if sorted_s in grouped_anagrams:
grouped_anagrams[sorted_s].append(s)
else:
grouped_anagrams[sorted_s] = [s]
return list(grouped_anagrams.values())