-
Notifications
You must be signed in to change notification settings - Fork 0
387. First Unique Character in a String #29
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
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,24 @@ | ||
/* | ||
Solve Time: 4:20 | ||
|
||
Time: O(n) | ||
Space: O(1) | ||
|
||
一回の走査で解けないかを軽く模索したが思いつかなかったので愚直にmapで記録し、二回ループする手法で解いた。 | ||
一回の走査で解くことに固執しかけて時間を無駄に思想になっていたため、そこに気づけた点が個人的によかった | ||
*/ | ||
class Solution { | ||
public: | ||
int firstUniqChar(string s) { | ||
map<char, int> character_to_count; | ||
for (const auto& c : s) { | ||
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. 型のサイズがレジスター幅以下の場合、 const 参照ではなく値のコピーで受け取ったほうが、処理が速くなると思います。理由は、 const 参照で受け取ると、値を参照する際にアドレスの計算が 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. コピーが発生しない分、参照が常に有利と考えていましたが、例外があるのですね 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. 現実問題、この場合はどちらも最適化されると思いますが、const 参照にする意図が不明とは感じますね。 呼出規約を確認ですかねえ。 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. なるほど、ありがとうございます、参考にします。 |
||
character_to_count[c]++; | ||
} | ||
for (int i = 0; i < s.size() ; i++) { | ||
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. find_if() 関数を使ったほうがよいかと一瞬思ったのですが、こちらのほうがシンプルだと思います。 |
||
if (character_to_count[s[i]] == 1) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
Time: O(n) | ||
Space: O(1) | ||
|
||
step1の改良、マジックナンバーを定数にした | ||
*/ | ||
class Solution { | ||
public: | ||
int firstUniqChar(string s) { | ||
map<char, int> character_to_count; | ||
for (const auto& c : s) { | ||
character_to_count[c]++; | ||
} | ||
for (int i = 0; i < s.size(); i++) { | ||
if (character_to_count[s[i]] == 1) { | ||
return i; | ||
} | ||
} | ||
|
||
int const kNotFound = -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.
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. これは書き方をミスっていました。 |
||
return kNotFound; | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
Time : O(N log N) | ||
Space : O(N) | ||
|
||
https://github.com/kazukiii/leetcode/pull/16/files#diff-f400dc0fa41a78f5b2a9ec5c2d5364dd8cf0da8dc9e8c39d15f52f98ea05394b | ||
を参考にした解法。 | ||
平衡木(mapのkey)を利用して文字の初出インデックスを管理。 | ||
二度目の出現でインデックスを削除することで、平衡木の先頭には一度しか出現していない文字のインデックスが残る。 | ||
3度以上の出現で削除した要素が復帰してしまうが、インデックスが大きいため結果に影響しない。 | ||
*/ | ||
class Solution { | ||
public: | ||
int firstUniqChar(string s) { | ||
map<char, int> character_to_first_index; | ||
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. 1-pass で処理するにあたり、文字をキューに入れていき、 2 回以上出現する文字を取り除いていくというやり方を考えました。 class Solution {
struct CharacterAndIndex {
char c;
int index;
};
public:
int firstUniqChar(string s) {
map<char, int> char_to_count;
queue<CharacterAndIndex> characters;
for (int i = 0; i < s.size(); ++i) {
char c = s[i];
characters.push({c, i});
++char_to_count[c];
while (!characters.empty() && char_to_count[characters.front().c] >= 2) {
characters.pop();
}
}
if (!characters.empty()) {
return characters.front().index;
}
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. おぉ、なるほど、これは直感的でわかりやすいですね。 |
||
map<int, char> index_to_character; | ||
for (int i = 0; i < s.size(); i++) { | ||
char c = s[i]; | ||
if (character_to_first_index.contains(c)) { | ||
int first_index = character_to_first_index[c]; | ||
index_to_character.erase(first_index); | ||
continue; | ||
} | ||
character_to_first_index[c] = i; | ||
index_to_character[i] = c; | ||
} | ||
if (index_to_character.empty()) { | ||
const int kNotFound = -1; | ||
return kNotFound; | ||
Comment on lines
+27
to
+28
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. これ、私はちょっとやりすぎな気がしていて、 return -1; // Not Found くらいのコメントでよいのではないでしょうか。 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. なるほど、そのあたりの感覚参考になります、ありがとうございます。 |
||
} | ||
return index_to_character.begin()->first; | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
https://github.com/colorbox/leetcode/pull/29#discussion_r1861430039 | ||
を見てそれを参考に自分で書いた解法、比較用 | ||
*/ | ||
class Solution { | ||
public: | ||
int firstUniqChar(string s) { | ||
queue<CharacterAndCount> queue; | ||
map<char, int> character_to_count; | ||
for (int i = 0; i < s.size(); i++) { | ||
char c = s[i]; | ||
character_to_count[c]++; | ||
queue.push({c, i}); | ||
while (true) { | ||
char front_character = queue.front().character; | ||
if (character_to_count[front_character] > 1) { | ||
queue.pop(); | ||
} else { | ||
break; | ||
} | ||
} | ||
} | ||
if (queue.empty()) { | ||
return -1; | ||
} | ||
return queue.front().index; | ||
} | ||
|
||
private: | ||
struct CharacterAndCount { | ||
char character; | ||
int index; | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
step2_2_suggested.cpp | ||
を | ||
https://github.com/colorbox/leetcode/pull/29#discussion_r1861430039 | ||
の書き方に沿って修正したコード、参照用 | ||
*/ | ||
class Solution { | ||
public: | ||
int firstUniqChar(string s) { | ||
queue<CharacterAndCount> characters; | ||
map<char, int> character_to_count; | ||
for (int i = 0; i < s.size(); ++i) { | ||
char c = s[i]; | ||
++character_to_count[c]; | ||
characters.push({c, i}); | ||
while (character_to_count[characters.front().character] >= 2) { | ||
characters.pop(); | ||
} | ||
} | ||
if (characters.empty()) { | ||
return -1; | ||
} | ||
return characters.front().index; | ||
} | ||
|
||
private: | ||
struct CharacterAndCount { | ||
char character; | ||
int index; | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
Time : O(N log N) | ||
Space : O(N) | ||
|
||
https://github.com/kazukiii/leetcode/pull/16/files#diff-f5b3276a9df261ed1c1e70d5b6de00ca4544e1a1ae1de6a466add8df5cd3d1b0 | ||
を参考にした解法。 | ||
ソートで文字種ごとに隣接させ、二度以上出現したものを除外し、min比較で最小インデックスを求める。 | ||
*/ | ||
class Solution { | ||
public: | ||
int firstUniqChar(string s) { | ||
vector<CharacterIndex> character_indices; | ||
for (int i = 0; i < s.size(); i++) { | ||
character_indices.push_back({s[i], i}); | ||
} | ||
sort(character_indices.begin(), character_indices.end()); | ||
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::ranges::sort を使う手、あと、lambda で何でソートするかを渡す手もあります。 |
||
int count = 1; | ||
int first_unique_character_index = numeric_limits<int>::max(); | ||
for (int i = 0; i < s.size(); i++) { | ||
if (i < s.size() - 1 && character_indices[i].character == character_indices[i + 1].character) { | ||
count++; | ||
continue; | ||
} | ||
if (count == 1) { | ||
first_unique_character_index = min(first_unique_character_index, character_indices[i].index); | ||
} | ||
count = 1; | ||
} | ||
Comment on lines
+19
to
+28
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. これちょっと読みにくい気がしますね。 for (int i = 0; i < character_indices.size();) {
int same_end;
for (same_end = i + 1; same_end < character_indices.size() && character_indices[i].character == character_indices[same_end].character; ++same_end);
if (same_end - i == 1) {
first_unique_character_index = min(first_unique_character_index, character_indices[i].index);
}
i = same_end;
} 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. なるほど、ありがとうございます。 |
||
if (first_unique_character_index == numeric_limits<int>::max()) { | ||
return -1; | ||
} | ||
return first_unique_character_index; | ||
} | ||
|
||
struct CharacterIndex { | ||
char character; | ||
int index; | ||
|
||
bool operator<(const CharacterIndex& other) { | ||
return character < other.character; | ||
} | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/* | ||
Time : O(N) | ||
Space : O(1) | ||
|
||
LRUを応用してできるとのことだったので実装。 | ||
https://www.geeksforgeeks.org/lru-cache-implementation-using-double-linked-lists/ | ||
本質的に平衡木による解法と大差ない気がする。 | ||
*/ | ||
struct Node { | ||
int index; | ||
int count; | ||
Node* prev; | ||
Node* next; | ||
|
||
explicit Node(int i) { | ||
index = i; | ||
count = 1; | ||
prev = nullptr; | ||
next = nullptr; | ||
} | ||
}; | ||
|
||
class LRUCache { | ||
public: | ||
Node* head; | ||
Node* tail; | ||
map<char, Node*> character_to_node; | ||
|
||
LRUCache() { | ||
head = new Node(numeric_limits<int>::max()); | ||
tail = new Node(numeric_limits<int>::max()); | ||
head->next = tail; | ||
tail->prev = head; | ||
} | ||
|
||
~LRUCache() { | ||
for (auto [_c, node] : character_to_node) { | ||
delete node; | ||
} | ||
delete head; | ||
delete tail; | ||
} | ||
|
||
void Put(char character, int index) { | ||
if (character_to_node.contains(character)) { | ||
Node *node = character_to_node[character]; | ||
Remove(node); | ||
node->count++; | ||
return; | ||
} | ||
|
||
Node *node = new Node(index); | ||
character_to_node[character] = node; | ||
PushBack(node); | ||
} | ||
|
||
bool Empty() { | ||
return head->next->index == tail->index; | ||
} | ||
|
||
private: | ||
void Remove(Node* node) { | ||
if (node->prev == nullptr) { | ||
return; | ||
} | ||
Node* prev = node->prev; | ||
Node* next = node->next; | ||
prev->next = next; | ||
next->prev = prev; | ||
node->prev = nullptr; | ||
node->next = nullptr; | ||
} | ||
|
||
void PushBack(Node* node) { | ||
Node* prev = tail->prev; | ||
prev->next = node; | ||
node->prev = prev; | ||
node->next = tail; | ||
tail->prev = node; | ||
} | ||
}; | ||
|
||
class Solution { | ||
public: | ||
int firstUniqChar(string s) { | ||
LRUCache cache = LRUCache(); | ||
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. LRU という名前は Least Recently Used という意味で、使わないやつから削除するということなので、現在の実装である同じものが2つ以上入ると Remove で linked list だけから削除されるというのは、ちょっと誤解を生む気がします。 出現回数を別の map で管理するか、重複したものを入れておく set を作るかして、このクラスをサイズ無制限の普通の LRU にしておくというのも一つでしょう。 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. ありがとうございます、LRUを改造して今回の問題に対応させましたが、ある程度改造した時点でもうLRUじゃないので名前を変えるなどすべきでした |
||
for (int i = 0; i < s.size(); i++) { | ||
cache.Put(s[i], i); | ||
} | ||
if (cache.Empty() || cache.head->next->count > 1) { | ||
return -1; | ||
} | ||
return cache.head->next->index; | ||
} | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
class Solution { | ||
public: | ||
int firstUniqChar(string s) { | ||
map<char, int> character_to_count; | ||
for (const char& c : s) { | ||
character_to_count[c]++; | ||
} | ||
for (int i = 0; i < s.size(); i++) { | ||
if (character_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.
文字列sの長さがnだとすると、空間計算量もそれに比例して大きくなると思ったのでO(n)になるのかな?と思ったのですが、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.
character_to_count
のみ考慮して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.
character_to_countが使うメモリ容量は文字列の長さ(n)に比例して大きくなるのでO(n)かと思ったのですが、どうでしょう?
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.
確かにそうでした!やっと理解できました。ありがとうございます。