-
Notifications
You must be signed in to change notification settings - Fork 0
387. First Unique Character in a String #16
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,45 @@ | ||
| ## 考察 | ||
| - 過去に解いたことあり(覚えていない) | ||
| - 方針 | ||
| - ソートして見ていく | ||
| - ソートすることにより同じ文字は隣合う | ||
| - 元のインデックスを保持しておく必要あり | ||
| - time: O(n lon n), space: O(n log n) -> time, spaceともにソートの分 | ||
| - 度数分布を取得してループ | ||
| - これも元のインデックスを保持する必要ありそう | ||
| - もしかしたらC++のunordered_mapは挿入順を保持する? | ||
|
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. あー、unordered_map がどんなものか確認しておいてください。 map は「挿入ではなくて key の」順序が保たれます。
Owner
Author
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. ありがとうございます。HashTableの標準的な実装方法について調べました。 以下、理解した内容です。 一方、mapはbalanced BST(標準的にはred-black tree)を使って実装されており、各ノードにkey, valueを持っており、keyの大小関係によって並び替えられる。 |
||
| - わからないのでドキュメント確認する必要 | ||
| - さくっと調べたが特にそのような記載は見当たらなかった | ||
| - https://en.cppreference.com/w/cpp/container/unordered_map | ||
| - time: O(n), space: O(n) | ||
| - 下の方針で行く | ||
| - あとは実装 | ||
|
|
||
| ## Step1 | ||
| - 上で考えたアルゴリズムを実装 | ||
| - time: O(n), space: O(n) | ||
|
|
||
| ## Step2 | ||
| - 他の人のPRを検索 | ||
| - Javaには順序のあるHashMapがあるらしい、平衡木を使う方法もある | ||
| - Ref. https://github.com/Ryotaro25/leetcode_first60/pull/16/files#r1642839594 | ||
|
|
||
| - Pythonのdictは挿入順序を保持するみたい | ||
| - Ref. https://github.com/nittoco/leetcode/pull/20/files#diff-c6374213bbbe17c2850ccd0bfb422c1f5bf40576b93320647cbfd9e9e3724c74R48 | ||
|
|
||
| - 文字列を2回走査すれば、インデックスを保持する必要はなかった | ||
| - これは思いつかなかった | ||
| - 時間的にも十分だし、一番シンプル | ||
| - https://github.com/nittoco/leetcode/pull/20/files#diff-c6374213bbbe17c2850ccd0bfb422c1f5bf40576b93320647cbfd9e9e3724c74R119 | ||
|
|
||
| - 上記を考慮して、C++なら文字列を2回走査するのが一番シンプルだと思ったので修正 | ||
|
|
||
| ## Step3 | ||
| - 1回目: 55s | ||
| - 2回目: 50s | ||
| - 3回目: 50s | ||
|
|
||
| ## Step4 | ||
| - 平衡木とソートを使った実装を追加した | ||
| - `step4_balanced_bst.cpp` | ||
| - `step4_sorting.cpp` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| class Solution { | ||
|
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. step2 のように、 index を保持しないほうがシンプルに感じます。 |
||
| public: | ||
| int firstUniqChar(string s) { | ||
| unordered_map<char, Count> char_to_count; | ||
| for (int i = 0; i < s.size(); ++i) { | ||
| char_to_count[s[i]].count++; | ||
| char_to_count[s[i]].index = i; | ||
| } | ||
|
|
||
| int answer = INT_MAX; | ||
|
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. step2 のように、見つかったら即 return した方がシンプルに感じます。 また、 INT_MAX より numeric_limits::max() を使用したほうが、 C++ 感があると思います。
Owner
Author
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. std::numeric_limits 知りませんでした。これから使っていこうと思います。 |
||
| for (const auto& [character, count]: char_to_count) { | ||
|
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. x64 アーキテクチャーを仮定するのであれば、値が 64 ビット (8 バイト) 以内に収まる場合は、参照にしないほうが良いと思います。理由は、値が 64 ビット (8 バイト) 以内に収まる場合、汎用レジスター 1 本に格納できるためです。参照にした場合、変数にアクセスする際に毎回ポインター経由でアクセスされるようになるため、定数倍遅くなります。 Effective C++ 第3版 あたりに書いてあったと思います。
Owner
Author
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. ありがとうございます、このようなコメント嬉しいです。CPUアーキテクチャについては詳しくないので、時間を取って勉強しようと思います。 |
||
| if (count.count == 1) { | ||
| answer = min(answer, count.index); | ||
| } | ||
| } | ||
|
|
||
| if (answer == INT_MAX) { | ||
| return -1; | ||
| } | ||
| return answer; | ||
| } | ||
|
|
||
| private: | ||
| struct Count { | ||
| int count; | ||
| int index; | ||
| }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| class Solution { | ||
| public: | ||
| int firstUniqChar(string s) { | ||
| unordered_map<char, int> char_to_count; | ||
| for (int i = 0; i < s.size(); i++) { | ||
| char_to_count[s[i]]++; | ||
| } | ||
|
|
||
| for (int i = 0; i < s.size(); i++) { | ||
| if (char_to_count[s[i]] == 1) return i; | ||
| } | ||
| return -1; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| class Solution { | ||
| public: | ||
| int firstUniqChar(string s) { | ||
| unordered_map<char, int> char_to_count; | ||
| for (int i = 0; i < s.size(); i++) { | ||
| char_to_count[s[i]]++; | ||
| } | ||
| for (int i = 0; i < s.size(); i++) { | ||
| if (char_to_count[s[i]] == 1) return i; | ||
| } | ||
| return -1; | ||
|
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.
みたいな
Owner
Author
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. コメントありがとうございます。マジックナンバーは極力避けた方が良いですね。 |
||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,17 @@ | ||||||
| class Solution { | ||||||
| public: | ||||||
| int firstUniqChar(string s) { | ||||||
| map<int, char> first_index_to_char; | ||||||
| unordered_map<char, int> char_to_first_index; | ||||||
|
Comment on lines
+4
to
+5
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. ここの変数名、ちょっと違和感があります。 "どのような構造か"は型情報から得られるので変数名に露出する必要は無いと思います
Owner
Author
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. ちょっと完全に意図が汲み取れていないのですが、mapの構造に関連するような 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)を入れる必要はない(又はもっと優先度の高い情報がある)という話ではないでしょうか。
Owner
Author
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の方ですね。おっしゃる通り対象の文字という意味で使っていました。 |
||||||
| for (int i = 0; i < s.size(); i++) { | ||||||
| if (char_to_first_index.find(s[i]) != char_to_first_index.end()) { | ||||||
| first_index_to_char.erase(char_to_first_index[s[i]]); | ||||||
| continue; | ||||||
| } | ||||||
| first_index_to_char[i] = s[i]; | ||||||
| char_to_first_index[s[i]] = i; | ||||||
| } | ||||||
|
|
||||||
| return !first_index_to_char.empty() ? first_index_to_char.begin()->first : -1; | ||||||
|
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
のほうがシンプルかなと思いました |
||||||
| } | ||||||
| }; | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| class Solution { | ||
| public: | ||
| int firstUniqChar(string s) { | ||
| vector<Character> s_with_index; | ||
| for (int i = 0; i < s.size(); i++) { | ||
| s_with_index.push_back({s[i], i}); | ||
| } | ||
| sort(s_with_index.begin(), s_with_index.end(), [](const Character& a, const Character& b) { | ||
| return a.character < b.character; | ||
| }); | ||
|
|
||
| int count = 1; | ||
| int answer = numeric_limits<int>::max(); | ||
| for (int i = 0; i < s_with_index.size(); i++) { | ||
| if (i < s_with_index.size() - 1 && s_with_index[i].character == s_with_index[i + 1].character) { | ||
| count++; | ||
| continue; | ||
| } | ||
| if (count == 1) { | ||
| answer = min(answer, s_with_index[i].index); | ||
| } | ||
| count = 1; | ||
| } | ||
|
|
||
| return answer != numeric_limits<int>::max() ? answer : -1; | ||
| } | ||
|
|
||
| private: | ||
| struct Character { | ||
| char character; | ||
| int index; | ||
| }; | ||
| }; |
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) ではないですか? in-place なら O(1) になると思います。
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(log n)の書き間違いでした。(C++のstd:sort()の標準の実装であるIntrosortはquick sortベースのため、最悪の場合はスタックサイズO(log n)使うという認識です)
ですが、この問題の場合元のインデックスを保持する関係でO(n)になりますね。。