-
Notifications
You must be signed in to change notification settings - Fork 0
49. Group Anagrams #17
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?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,116 @@ | ||||||
| ## Step1 | ||||||
| 同じ文字構成になる単語を分類する問題. 文字構成が同じということは各文字をカウントしてその結果が同じ単語をグループにすれば解けそう. オーソドックスにhashmapを用いて文字の出現をカウントする. | ||||||
| また,文字構成が同じ単語をグループにするためにhashmapを用いる. | ||||||
| hashmapはそのままでは`hashable`(immutable)ではないので,dictのキーにすることができない. そこで,dictの要素から無理やりユニークなハッシュ値を作成してしまえばいいのではと考え,{文字}{出現回数}の列をキーとした. | ||||||
| 以下のように実装をしてAccept. | ||||||
| 47ms(かなり遅め). | ||||||
| 時間計算量はO(N*(LlogL)) N:strsの単語数, L:各単語の平均長. 各文字のカウント&ソート処理で, 単語長Lの走査でO(L)であり,dictのソート処理は英小文字に限定すれば最大26種類なので定数時間になるが,一般的なUnicodeを想定して最大O(L*logL)になる. | ||||||
|
|
||||||
| ```py | ||||||
| class Solution: | ||||||
| def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||||||
| def get_char_freq(word): | ||||||
| char_to_freq = defaultdict(int) | ||||||
| for char in word: | ||||||
| char_to_freq[char] += 1 | ||||||
|
|
||||||
| return dict(sorted(char_to_freq.items())) | ||||||
|
|
||||||
| def dict_to_hash(char_to_freq): | ||||||
| hash = "" | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "hash"は組み込み関数と被っているので避けたいですね。 |
||||||
| for char, freq in char_to_freq.items(): | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. charはCやJavaの組み込み型と被るので自分は避けます。 cやchがよくみます。 蛇足ですが、chr()はPythonの組み込み関数で、ord()の逆操作(コードポイント -> 文字へ変換)をする関数です。 |
||||||
| # 文字 + 出現回数 | ||||||
| hash = f"{hash}{char}{str(freq)}" | ||||||
|
|
||||||
| return hash | ||||||
|
|
||||||
| wordcount_to_list = defaultdict(list) | ||||||
| for word in strs: | ||||||
| char_to_freq = get_char_freq(word) | ||||||
| wordcount_to_list[dict_to_hash(char_to_freq)].append(word) | ||||||
|
|
||||||
| return list(wordcount_to_list.values()) | ||||||
| ``` | ||||||
| hashの変換の効率が悪い. hashを毎回連結して代入しているのでこれだと毎回文字列のコピーが発生し,計算量は最悪(L^2)に. `tuple`はimmutableなのでタプルに変換してキーにすれば高速にhash化できる. | ||||||
| 他には`frozenset`もimmutableだが追加処理が遅い. | ||||||
| ```py | ||||||
| class Solution: | ||||||
| def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||||||
| def get_char_frequency(word): | ||||||
| char_to_freq = defaultdict(int) | ||||||
| for char in word: | ||||||
| char_to_freq[char] += 1 | ||||||
|
|
||||||
| return char_to_freq | ||||||
|
|
||||||
| def dict_to_hashable(char_to_freq): | ||||||
| return tuple(sorted(char_to_freq.items())) | ||||||
|
|
||||||
| charcount_to_anagram = defaultdict(list) | ||||||
| for word in strs: | ||||||
| char_to_freq = get_char_frequency(word) | ||||||
| charcount_to_anagram[dict_to_hashable(char_to_freq)].append(word) | ||||||
|
|
||||||
| return list(charcount_to_anagram.values()) | ||||||
| ``` | ||||||
|
|
||||||
|
|
||||||
| Acceptされてから, 文字列自体をソートしてキーにしまえば一意になることに気がついたので実装を変更. (文字列はhashableなのでキーにできる) | ||||||
| 時間計算量: O(N*L*logL) N: strs.length, L: strs[i].length | ||||||
| `str()`でL^2かかる? | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. L でいきます。 |
||||||
| 19ms | ||||||
| ```py | ||||||
| class Solution: | ||||||
| def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||||||
| uniqeword_to_anagrams = defaultdict(list) | ||||||
| for word in strs: | ||||||
| uniqeword_to_anagrams["".join(sorted(word))].append(word) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. uniqe -> unique
Suggested change
|
||||||
|
|
||||||
| return list(uniqeword_to_anagrams.values()) | ||||||
| ``` | ||||||
|
|
||||||
| `str`はこの場合だとlist全体を文字列化してしまうので,`join`を使うほうがいい. | ||||||
|
|
||||||
| ```diff | ||||||
| --- uniqueword_to_list[str(sorted(word))].append(word) | ||||||
| +++ uniqueword_to_list["".join(sorted(word))].append(word) | ||||||
| ``` | ||||||
|
|
||||||
| ## Step2 コード,コメントを読む | ||||||
| https://github.com/azriel1rf/leetcode-prep/pull/4#discussion_r1973077272 | ||||||
| >ord からフォローアップの質問でユニコードのコードポイントの話などが想定されます。 | ||||||
| >また、入力が、小文字アルファベットでないものが来たときに、どのような振る舞いをするか、どのような振る舞いをするべきかは追加質問が来てもおかしくないでしょう。 | ||||||
|
|
||||||
| `ord`を使った解法でまずは解いてみる. 時間計算量はO(NL), 空間計算量O(NL). | ||||||
| memo: アルファベット小文字のUnicodeポイントは97 ~ 122, 大文字は65 ~ 50らしい | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 上記のフォローアップ質問について, 大文字ならG ~ Zまでは用意した配列に加算されるが数値が文字列に含まれていた場合範囲外アクセスになる.('9'のコードポイントは57) | ||||||
| ```py | ||||||
| class Solution: | ||||||
| def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||||||
| counter_to_anagrams = defaultdict(list) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. count_to_anagramsの方が自然かなと思いました。英語に弱いので自分でもなぜ違和感を感じたのかはっきりしなかったのですが、counterは計算する人、計数器という意味があるそうで、key_value形式のkeyに使うのであればcount(数える)の方が自然に感じるという感覚です。 |
||||||
| NUM_CHARACTERS = 26 | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 26がどこから来たのかを明示するために、len(string.ascii_lowercase)と書いても良いでしょう。 https://docs.python.org/ja/3/library/string.html#string.ascii_lowercase |
||||||
| BASE_UNICODE_POINT = ord('a') | ||||||
|
|
||||||
| for word in strs: | ||||||
| counts = [0] * NUM_CHARACTERS | ||||||
| for char in word: | ||||||
| counts[ord(char) - BASE_UNICODE_POINT] += 1 | ||||||
|
|
||||||
| counter_to_anagrams[tuple(counts)].append(word) | ||||||
|
|
||||||
| return list(counter_to_anagrams.values()) | ||||||
| ``` | ||||||
| - 入力の制約,範囲などは常に先に考えるようにしたい. | ||||||
|
|
||||||
|
|
||||||
| ## Step3 | ||||||
| これが一番シンプルかつ様々な入力に対応できている. | ||||||
| ```py | ||||||
| class Solution: | ||||||
| def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||||||
| uniqeword_to_anagrams = defaultdict(list) | ||||||
| for word in strs: | ||||||
| uniqeword_to_anagrams["".join(sorted(word))].append(word) | ||||||
|
|
||||||
| return list(uniqeword_to_anagrams.values()) | ||||||
| ``` | ||||||
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://ja.wikipedia.org/wiki/連長圧縮