Skip to content

Commit d70dfa9

Browse files
authored
Merge pull request #12 from Hurukawa2121/group-anagrams
49. Group Anagrams
2 parents 4a8e93e + b01f093 commit d70dfa9

File tree

5 files changed

+151
-0
lines changed

5 files changed

+151
-0
lines changed

4.hash-map/group-anagrams/memo.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
## step1
2+
- 要素の種類とそれぞれの個数が同じ文字列をグルーピングする。
3+
- 二つ指針がある。
4+
- 文字列をソートして一致判定
5+
- 要素をカウントして一致判定
6+
- カウントの処理がない分、周辺の処理が簡潔になるため文字列をソートして一致判定する。
7+
- 文字列の数をN,文字列の長さをMとすると、時間計算量O(Nlog(M)+N), 空間計算量O(N)
8+
- 制約から1.4*10^5(0.001秒)ほど
9+
10+
- ソートした文字列を格納する `std::map``std::unordered_map` の比較は難しい。
11+
- キーをソートする必要がないので `std::unordered_map` としたいが、以下のリンクの通り整数徐算が遅くなる場合がある。そこに留意して使う。
12+
- https://chromium.googlesource.com/chromium/src/+/master/base/containers/README.md#std_unordered_map-and-std_unordered_set
13+
14+
- 以下の処理が不安
15+
```cpp
16+
sorted_str_to_anagram_group[sorted_str].push_back(str);
17+
```
18+
- `sorted_str``anagram_group` に紐づいているか、読み手が直感的に分からない可能性がある。
19+
- ここが問題になる場合の書き方が分からなかった。
20+
21+
## step2
22+
- 他のコードを読む。
23+
24+
- 以下なら値のコピーを避け、不可変にすることができる。
25+
```cpp
26+
...
27+
for (const std::string& str : strs) {
28+
...
29+
```
30+
31+
- C++ 17 から `std::transform` の記法があるらしい。
32+
- https://en.cppreference.com/w/cpp/algorithm/transform
33+
```cpp
34+
transform(begin(sorted_str_to_anagram_group), end(sorted_str_to_anagram_group), back_inserter(anagram_groups), [](const auto& groups) {
35+
return groups.second;
36+
});
37+
```
38+
- 標準ライブラリの多くが `constexpr` をサポートしてなかったことから出来たらしい。
39+
- https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0202r3.html#:~:text=I.%20Introduction%20and%20Motivation
40+
- 分かりやすいのでstep1の書き方にする。
41+
42+
## step3
43+
- 補完無しで3分50秒ほどで実装

4.hash-map/group-anagrams/step1.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <string>
2+
#include <map>
3+
4+
class Solution {
5+
public:
6+
std::vector<std::vector<std::string>> groupAnagrams(std::vector<std::string>& strs) {
7+
std::unordered_map<std::string, std::vector<std::string>> sorted_str_to_anagram_group;
8+
for (std::string str : strs) {
9+
std::string sorted_str = str;
10+
std::sort(sorted_str.begin(), sorted_str.end());
11+
sorted_str_to_anagram_group[sorted_str].push_back(str);
12+
}
13+
14+
std::vector<std::vector<std::string>> anagram_groups;
15+
for (auto [__maybe_unused, anagram_group] : sorted_str_to_anagram_group) {
16+
anagram_groups.push_back(anagram_group);
17+
}
18+
return anagram_groups;
19+
}
20+
};

4.hash-map/group-anagrams/step2.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <string>
2+
#include <map>
3+
4+
class Solution {
5+
public:
6+
std::vector<std::vector<std::string>> groupAnagrams(std::vector<std::string>& strs) {
7+
std::unordered_map<std::string, std::vector<std::string>> sorted_str_to_anagram_group;
8+
for (const std::string& str : strs) {
9+
std::string sorted_str = str;
10+
std::sort(sorted_str.begin(), sorted_str.end());
11+
sorted_str_to_anagram_group[sorted_str].push_back(str);
12+
}
13+
14+
std::vector<std::vector<std::string>> anagram_groups;
15+
for (const auto& [__maybe_unused, anagram_group] : sorted_str_to_anagram_group) {
16+
anagram_groups.push_back(anagram_group);
17+
}
18+
return anagram_groups;
19+
}
20+
};

4.hash-map/group-anagrams/step3.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <string>
2+
#include <map>
3+
4+
class Solution {
5+
public:
6+
std::vector<std::vector<std::string>> groupAnagrams(std::vector<std::string>& strs) {
7+
std::unordered_map<std::string, std::vector<std::string>> sorted_str_to_anagram_group;
8+
for (const std::string& str : strs) {
9+
std::string sorted_str = str;
10+
std::sort(sorted_str.begin(), sorted_str.end());
11+
sorted_str_to_anagram_group[sorted_str].emplace_back(str);
12+
}
13+
14+
std::vector<std::vector<std::string>> anagram_groups;
15+
for (const auto& [__maybe_unused, anagram_group] : sorted_str_to_anagram_group) {
16+
anagram_groups.push_back(anagram_group);
17+
}
18+
return anagram_groups;
19+
}
20+
};

4.hash-map/group-anagrams/step4.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include <map>
2+
#include <string>
3+
#include <functional>
4+
5+
class Solution {
6+
public:
7+
std::vector<std::vector<std::string>> groupAnagrams(std::vector<std::string>& strs) {
8+
std::unordered_map<StringAnalysis, std::vector<std::string>, HashStringAnalysis> frequencies_to_anagram_groups;
9+
for (const std::string str : strs) {
10+
// ASCIIに基づいて数え上げると EBCDIC などで動かなくなる可能性がある
11+
StringAnalysis analysis(str);
12+
frequencies_to_anagram_groups[analysis].push_back(str);
13+
}
14+
15+
std::vector<std::vector<std::string>> anagram_groups;
16+
for (const auto& [_, anagram_group] : frequencies_to_anagram_groups) {
17+
anagram_groups.push_back(anagram_group);
18+
}
19+
return anagram_groups;
20+
}
21+
22+
private:
23+
struct StringAnalysis {
24+
std::map<char, int> frequencies;
25+
StringAnalysis (const std::string str) :frequencies() {
26+
for (char c : str) {
27+
frequencies[c]++;
28+
}
29+
}
30+
bool operator==(const StringAnalysis& other) const {
31+
return frequencies == other.frequencies;
32+
}
33+
};
34+
35+
struct HashStringAnalysis {
36+
std::size_t operator()(const StringAnalysis &sa) const noexcept {
37+
std::size_t hash = 0;
38+
for (auto &[key, value] : sa.frequencies) {
39+
size_t hashed_key = std::hash<char>{}(key);
40+
size_t hashed_value = std::hash<int>{}(value);
41+
// 黄金比+シフト+XORで散らばらせビットパターンの衝突確率を下げる
42+
// https://qiita.com/iroha1203/items/3e22e6e751189db6644a
43+
hash ^= (hashed_key ^ (hashed_value + 0x61C88647ULL + (hash << 6) + (hash >> 2)));
44+
}
45+
return hash;
46+
}
47+
};
48+
};

0 commit comments

Comments
 (0)