-
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?
Conversation
| - ソートして見ていく | ||
| - ソートすることにより同じ文字は隣合う | ||
| - 元のインデックスを保持しておく必要あり | ||
| - time: O(n lon n), space: O(n log n) -> time, spaceともにソートの分 |
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)になりますね。。
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;
};
};| @@ -0,0 +1,28 @@ | |||
| class Solution { | |||
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.
step2 のように、 index を保持しないほうがシンプルに感じます。
| char_to_count[s[i]].index = i; | ||
| } | ||
|
|
||
| int answer = INT_MAX; |
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.
step2 のように、見つかったら即 return した方がシンプルに感じます。
また、 INT_MAX より numeric_limits::max() を使用したほうが、 C++ 感があると思います。
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.
std::numeric_limits 知りませんでした。これから使っていこうと思います。
https://en.cppreference.com/w/cpp/types/numeric_limits
| } | ||
|
|
||
| int answer = INT_MAX; | ||
| for (const auto& [character, count]: char_to_count) { |
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.
x64 アーキテクチャーを仮定するのであれば、値が 64 ビット (8 バイト) 以内に収まる場合は、参照にしないほうが良いと思います。理由は、値が 64 ビット (8 バイト) 以内に収まる場合、汎用レジスター 1 本に格納できるためです。参照にした場合、変数にアクセスする際に毎回ポインター経由でアクセスされるようになるため、定数倍遅くなります。
ただ、実際にはコンパイラーが最適化してくれる可能性もあります。生成されるアセンブラーを確認したほうが良いかもしれません。
Effective C++ 第3版
20項 値渡しよりconst参照渡しを使おう
https://www.maruzen-publishing.co.jp/item/b294734.html
あたりに書いてあったと思います。
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.
ありがとうございます、このようなコメント嬉しいです。CPUアーキテクチャについては詳しくないので、時間を取って勉強しようと思います。
現状、汎用レジスターのサイズ = 値渡しか参照渡しかの判断基準 と暗記しておきます。(現代のコンピュータでは一般に64bit)
| - 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 comment
The reason will be displayed to describe this comment to others. Learn more.
あー、unordered_map がどんなものか確認しておいてください。
map は「挿入ではなくて 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.
ありがとうございます。HashTableの標準的な実装方法について調べました。
以下、理解した内容です。
HashTableは、標準的にはbucketの配列として実装される。keyのハッシュ値に基づいて各バケットに分配。
なので、iteratorを使って挿入した順に取り出せるという保証は全くない。
ハッシュ衝突の解決には、一般にchain法とopen address法の2種類ある。C++ではchain法で実装されることが多い。(cpythonはopen address法で実装されているようです)
chain法で衝突を解決する場合、bucketの実装にはlinked listを使用する。
一方、mapはbalanced BST(標準的にはred-black tree)を使って実装されており、各ノードにkey, valueを持っており、keyの大小関係によって並び替えられる。
https://en.cppreference.com/w/cpp/container/map
| 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 comment
The reason will be displayed to describe this comment to others. Learn more.
-1に名前をつけてあげるのも可読性をあげる選択肢かなと思います
const int NOT_FOUND = -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.
コメントありがとうございます。マジックナンバーは極力避けた方が良いですね。
| map<int, char> first_index_to_char; | ||
| unordered_map<char, int> char_to_first_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.
ここの変数名、ちょっと違和感があります。
まず、変数名は"それが何であるか"を示すものという感覚があります。
"どのような構造か"は型情報から得られるので変数名に露出する必要は無いと思います
first_index_to_charとchar_to_first_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.
ちょっと完全に意図が汲み取れていないのですが、mapの構造に関連するような x_to_y のような変数名は認知不可が高いということでしょうか?
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.
恐らく変数名に型情報(今回の場合はchar)を入れる必要はない(又はもっと優先度の高い情報がある)という話ではないでしょうか。
多分charは型情報というよりは対象の文字という意味で使われているとは思うのですが。
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.
あ、charの方ですね。おっしゃる通り対象の文字という意味で使っていました。
とはいえ、型名と被ると混乱しますね。気をつけます。
| 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 comment
The reason will be displayed to describe this comment to others. Learn more.
| return !first_index_to_char.empty() ? first_index_to_char.begin()->first : -1; | |
| return first_index_to_char.empty() ? -1: first_index_to_char.begin()->first; |
のほうがシンプルかなと思いました
問題へのリンク
https://leetcode.com/problems/first-unique-character-in-a-string/description/
次に解く問題
560. Subarray Sum Equals K
README.mdへ頭の中の言語化と記録をしています。