-
Notifications
You must be signed in to change notification settings - Fork 0
49 group anagrams medium #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
49_group_anagrams_medium/step2.py
Outdated
| return list(anagram_groups.values()) | ||
|
|
||
| # canonical key of a word is generated by sorting each characters in it | ||
| def generate_anagram_key(word: str) -> str: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self を引数に書き忘れていますね。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
レビューありがとうございます。おっしゃるとおりですね、きちんと通るかを確認してpushします。
| anagram_groups[canonical_key].append(word) | ||
| return list(anagram_groups.values()) | ||
|
|
||
| # canonical key of a word is generated by sorting each characters in it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
関数やメソッドに関するコメントは def の次の行に docstring として書くことが一般的だと思います。
https://google.github.io/styleguide/pyguide.html#383-functions-and-methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
たしかにその通りですね。style guideまで案内ありがとうございます。
| anagram_groups[canonical_key].append(word) | ||
| return list(anagram_groups.values()) | ||
|
|
||
| def generate_anagram_key(self, word: str) -> tuple[str]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
返り値は tuple[int, ...] でしょうか。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これも同じくおっしゃるとおりです。
古いtype hintが残ったまま、更新できていませんでした。
49_group_anagrams_medium/README.md
Outdated
| `strs`の配列の長さを`n`、各文字列の長さの最大値を`m`、各文字列の含む文字の種類数を`k`とすると | ||
| 本問では、`k= 26`(英小文字のみ)である。 | ||
| - 時間計算量:`O(n * k)` | ||
| - 空間計量:`O(n * m)` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
L32とL33は逆でしょうか。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ご指摘ありがとうございます。結論としては、どちらの計算量も間違っており、正しくは
- 時間計算量:
O(n * m) - 空間計量:
O(n * (m + k))
でした。以下、この理由を説明します。
まず、別解ではkeyをsorted_word: strから (cnt_1, cnt_2, ..., cnt_k): tuple[int, ...] の形式にすることを考えています。
時間計算量について
wordの文字数をm、文字の種類数をkとします。(小文字英字のみを考えると、k = 26です。)
wordに対するkeyの生成一回あたりの時間計算量は、wordの各文字を見ていくのでO(m)です。- よって、全体の時間計算量は
O(n * m)となります。
空間計算量について
keyひとつあたりの空間計算量はO(k)です。wordひとつあたりの空間計算量はO(m)です。- よって、全体の(最悪)空間計算量は
O(n * (m + k))となります。- すべての
wordが異なる文字を持つ場合が最悪ケースです。
- すべての
各wordの文字数が大きすぎると、各keyの長さが大きくなり無駄が多くなるのではないか、という課題感からこの別解を考えましたが、実際に改善されるのはキーのソートが無くなる部分のlog(m)だけで、あまり改善されないことがわかりました。ご指摘いただきありがとうございました。
| class Solution: | ||
| def groupAnagrams(self, words: list[str]) -> list[list[str]]: | ||
| # manage anagrams as a hashmap whose key is "canonical key" and value is a list of anagrams | ||
| anagram_groups: dict[tuple[str], list[str]] = defaultdict(list) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
anagram_groups: dict[tuple[int, ...], list[str]] = defaultdict(list) でしょうか。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
毎度レビューありがとうございます。おっしゃるとおりです。
古いtype hintが残ったまま、更新できていませんでした。本末転倒ですね。
| anagram_groups[canonical_key].append(word) | ||
| return list(anagram_groups.values()) | ||
|
|
||
| def generate_anagram_key(self, word: str) -> tuple[str]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
他のクラスから呼ばれない protected な関数のため、 _generate_anagram_key とするとよいと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
確かにその通りですね。アドバイスありがとうございます。
| def groupAnagrams(self, strs: list[str]) -> list[list[str]]: | ||
|
|
||
| # hasmap: standardized key -> list of anagrams | ||
| hashmap = defaultdict(list) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
変数名に型名を入れても、読み手にとってあまり有益な情報にはならないと思います。キーと値にどのような値が含まれているかを明示することをおすすめします。 sorted_to_anagrams などはいかがでしょうか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
それはその通りですね。
sorted_to_anagrams はわかりやすいと感じました。dictは XX_to_YY のスタイルで書けないか、検討することにします。
Kaichi-Irie
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
レビューとご指摘ありがとうございます。
| anagram_groups[canonical_key].append(word) | ||
| return list(anagram_groups.values()) | ||
|
|
||
| # canonical key of a word is generated by sorting each characters in it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
たしかにその通りですね。style guideまで案内ありがとうございます。
49_group_anagrams_medium/README.md
Outdated
| `strs`の配列の長さを`n`、各文字列の長さの最大値を`m`、各文字列の含む文字の種類数を`k`とすると | ||
| 本問では、`k= 26`(英小文字のみ)である。 | ||
| - 時間計算量:`O(n * k)` | ||
| - 空間計量:`O(n * m)` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ご指摘ありがとうございます。結論としては、どちらの計算量も間違っており、正しくは
- 時間計算量:
O(n * m) - 空間計量:
O(n * (m + k))
でした。以下、この理由を説明します。
まず、別解ではkeyをsorted_word: strから (cnt_1, cnt_2, ..., cnt_k): tuple[int, ...] の形式にすることを考えています。
時間計算量について
wordの文字数をm、文字の種類数をkとします。(小文字英字のみを考えると、k = 26です。)
wordに対するkeyの生成一回あたりの時間計算量は、wordの各文字を見ていくのでO(m)です。- よって、全体の時間計算量は
O(n * m)となります。
空間計算量について
keyひとつあたりの空間計算量はO(k)です。wordひとつあたりの空間計算量はO(m)です。- よって、全体の(最悪)空間計算量は
O(n * (m + k))となります。- すべての
wordが異なる文字を持つ場合が最悪ケースです。
- すべての
各wordの文字数が大きすぎると、各keyの長さが大きくなり無駄が多くなるのではないか、という課題感からこの別解を考えましたが、実際に改善されるのはキーのソートが無くなる部分のlog(m)だけで、あまり改善されないことがわかりました。ご指摘いただきありがとうございました。
| class Solution: | ||
| def groupAnagrams(self, words: list[str]) -> list[list[str]]: | ||
| # manage anagrams as a hashmap whose key is "canonical key" and value is a list of anagrams | ||
| anagram_groups: dict[tuple[str], list[str]] = defaultdict(list) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
毎度レビューありがとうございます。おっしゃるとおりです。
古いtype hintが残ったまま、更新できていませんでした。本末転倒ですね。
| anagram_groups[canonical_key].append(word) | ||
| return list(anagram_groups.values()) | ||
|
|
||
| def generate_anagram_key(self, word: str) -> tuple[str]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これも同じくおっしゃるとおりです。
古いtype hintが残ったまま、更新できていませんでした。
| anagram_groups[canonical_key].append(word) | ||
| return list(anagram_groups.values()) | ||
|
|
||
| def generate_anagram_key(self, word: str) -> tuple[str]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
確かにその通りですね。アドバイスありがとうございます。
| def groupAnagrams(self, strs: list[str]) -> list[list[str]]: | ||
|
|
||
| # hasmap: standardized key -> list of anagrams | ||
| hashmap = defaultdict(list) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
それはその通りですね。
sorted_to_anagrams はわかりやすいと感じました。dictは XX_to_YY のスタイルで書けないか、検討することにします。
…nts and update README with complexity analysis
| 時間計算量について | ||
| `word`の文字数を`m`、文字の種類数を`k`とします。(小文字英字のみを考えると、`k = 26`です。) | ||
| - `word`に対する`key`の生成一回あたりの時間計算量は、`word`の各文字を見ていくので`O(m)`です。 | ||
| - よって、全体の時間計算量は`O(n * m)`となります。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
計算量を見積もるのもいいんですが、計算量は計算時間を見積もる手段なので、計算時間まで見積もってください。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
アドバイスありがとうございます。いわゆる「計算量」は漸近的な挙動しかとらえていないというのは常に忘れないようにします。一方で、leetcodeの問題を解く際に意識できる計算時間というのは、せいぜい単純な演算の回数のことでしょうか。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
分岐予測にどれくらい失敗しそうだとか、キャッシュミスをどれくらいしそうかとか、通信時間とか、そういったものも考えています。
たとえば、ここでリアロケーションの時間をナノ秒単位で考えています。
https://discord.com/channels/1084280443945353267/1237649827240742942/1359177255158419638
Python だとインタープリターのために見積もりはより難しいですが、ネイティブコードにすると20倍くらい変わったりします。
https://discord.com/channels/1084280443945353267/1367399154200088626/1372840818397810701
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
レビューありがとうございます。リアロケーションの議論はとても興味深く拝見しました。
オーダー記法による概算を超えた、Pythonの場合の各計算時間の勘所みたいなものがまだまだないので、気にしていきます。このあたりの時間感覚みたいなものを鍛えていきたいですね。
キャッシュミスについては、 「プログラマーですがなぜキャッシュメモリは早いのかといった物理的なことがネットで調べてもしっかり理解できまえせん」に関して #CPU - Qiitaあたりも読みました。
また、odaさんがいつか紹介されていた"Numbers Everyone Should Know" from Jeff Dean.の推移が書いてあるwebサイトも見つけました。Numbers Every Programmer Should Know By Year
問題へのリンク
Group Anagrams - LeetCode
言語
Python
自分の解法
strsの各文字列を順番に走査し、各文字列をソートした結果をキーとして、ハッシュマップanagram_groups: dict[str, list[str]]に格納する。アナグラム同士は、同じソート結果を持つため、同じキーに格納される。
strsの配列の長さをn、各文字列の長さの最大値をmとする。本問では、
0<= n <= 10^4、0 <= m <= 100である。O(n * m log(m))O(n * m)step2
anagram_groupsと命名。generate_anagram_key関数に切り出す。canonical_keyと命名。(アナグラムの正規形を表すキー、の意)別解・模範解答(
char_count_key.py)もし、
strsの各文字列の長さが長い場合、ソートにかかる時間が大きくなるため、キーを文字列のカウントのタプルにする方法も考えられる。ここで、ハッシュマップのキーに使えるのは、イミュータブルなオブジェクトである必要があるため、リストではなくタプルを使う。
strsの配列の長さをn、各文字列の長さの最大値をm、各文字列の含む文字の種類数をkとすると本問では、
k= 26(英小文字のみ)である。O(n * k)O(n * m)次に解く問題の予告