-
Notifications
You must be signed in to change notification settings - Fork 0
387. First Unique Character in a String #21
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
ad9c8ca
50bcb40
5000d88
78368b2
5282f2d
cf3b44c
9c948c6
e96141f
40002cb
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,290 @@ | ||
| # 387. First Unique Character in a String | ||
|
|
||
| ## STEP1 | ||
|
|
||
| ### 発想 | ||
| * 文字ごとの出現回数を数える。 | ||
| * 先頭から1つずつ走査していく。 | ||
| * 出現回数が1の文字があった際には、そのインデックスを返却する。 | ||
|
|
||
| ```javascript | ||
| const firstUniqChar = function(s) { | ||
| const char_to_count = new Map() | ||
| for (const ch of s) { | ||
| if (!char_to_count.has(ch)) { | ||
| char_to_count.set(ch, 0) | ||
| } | ||
| const count = char_to_count.get(ch) | ||
| char_to_count.set(ch, count + 1) | ||
| } | ||
| for (let i = 0; i < s.length; i++) { | ||
| const ch = s[i] | ||
| if (char_to_count.get(ch) === 1) { | ||
| return i | ||
| } | ||
| } | ||
| return -1 | ||
| }; | ||
| ``` | ||
|
|
||
| ## STEP2 | ||
|
|
||
| * pythonのdefaultdictのように、mapのkeyが存在しない際に0が入るように、 | ||
| || を用いて、書くほうが好みなのでこちらに修正した。 | ||
|
|
||
| ```javascript | ||
| const firstUniqChar = function(s) { | ||
| const charToCount = new Map() | ||
| for (const c of s) { | ||
| const count = charToCount.get(c) || 0 | ||
| charToCount.set(c, count + 1) | ||
| } | ||
| for (let i = 0; i < s.length; i++) { | ||
| if (charToCount.get(s[i]) === 1) { | ||
| return i | ||
| } | ||
| } | ||
| return -1 | ||
| }; | ||
| ``` | ||
|
|
||
| ## STEP3 | ||
|
|
||
| * 1分、1分、1分 | ||
|
|
||
| ```javascript | ||
| const firstUniqChar = function(s) { | ||
| const charToCount = new Map() | ||
| for (const c of s) { | ||
| const count = charToCount.get(c) || 0 | ||
| charToCount.set(c, count + 1) | ||
| } | ||
| for (let i = 0; i < s.length; i++) { | ||
| if (charToCount.get(s[i]) === 1) { | ||
| return i | ||
| } | ||
| } | ||
| return -1 | ||
| }; | ||
| ``` | ||
|
|
||
| ## 感想 | ||
|
|
||
| * For文が2周していた際に、1周でできないかと考えるのは、習慣化できそう。 | ||
|
|
||
| * LinkedHashMap というデータ構造については、次に解くLRUの問題を解いた際に学習する。 | ||
|
|
||
| ### コメント集を読んで | ||
|
|
||
| ## 他の人のPRを読んで | ||
|
|
||
| * hayashi-ayのコメント | ||
| * Mapではなく、配列で出現回数を保持する方法がある。 | ||
| * 参照: https://discord.com/channels/1084280443945353267/1233603535862628432/1237973103670198292 | ||
|
|
||
| * UTF-8, UTF-16 | ||
| https://github.com/ichika0615/arai60/blob/96c3a36d478787dbe6fe0c03a45bc1e392be386d/arai60_15.md#%E4%BB%A5%E4%B8%8B%E5%8B%89%E5%BC%B7 | ||
|
|
||
| * Ryotaro25 | ||
| * PR: https://github.com/Ryotaro25/leetcode_first60/pull/16/ | ||
| * 387.FirstUniqueCharacterinaString/step4.cpp の参照を実装している箇所が読めない。 | ||
| --> ポインターや参照の箇所が理解できていない。次の問題までに勉強する。 | ||
|
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. これ的外れだったら放念してください. #include <stdio.h>
void f(int a) {
a = 10;
}
void g(int *a) {
*a = 10;
}
int main() {
int a = 0;
f(a); // 変化しない
printf("%d\n", a); // 出力: 0
g(&a); // 変化する,`&`はアドレスを取得する演算子
printf("%d\n", a); // 出力: 10
}そこで,Cではしばしば,ポインタの書き換えを呼び出した関数内で行う,ということになると,ポインタのポインタのポインタの...というような面倒なことになることがあります. 一方,C++ではこの部分のインターフェースを参照という形で直感的で簡潔にしてくれていて,明示的にポインタを用いないでも,内部にポインタを用いて呼び出した関数内での変更を反映できるようになっています. 上の例で行けば, #include <stdio.h>
// 引数が,`&`付きで,参照になっている
void f(int& a) {
a = 10; // デリファレンスはしてないが,呼び出し元が書き換わる
}
int main() {
int a = 0;
f(a); // 今度はポインタを取らないが
printf("%d\n", a); // これは10になっている
}この方は,この参照の機能を自分で,Cライクな様式である,ポインタを用いて再実装してみた,ということをされているのだと思います. |
||
|
|
||
| * Yoshiki-Iwasa | ||
| * PR: https://github.com/Yoshiki-Iwasa/Arai60/pull/14 | ||
| * 思考プロセスが記述されていて、すごくレビューがしやすくなると思うので、自分も真似をする。 | ||
| * rustは文法が理解できていないので、公式ドキュメントを見ながら文法を検索できるようにする。 | ||
|
|
||
| * olsen-blue | ||
| * PR: https://github.com/olsen-blue/Arai60/pull/15/ | ||
| * return -1とする際には、関数の呼び出し側でコメントを書くことがある。 | ||
| https://github.com/olsen-blue/Arai60/pull/15/files#r1913610172 | ||
| * 多言語で予約後となっているものは、変数名として避けた方が良い。 | ||
| https://github.com/olsen-blue/Arai60/pull/15/files#r1941265089 | ||
|
|
||
| * hayashi-ay | ||
| * PR: https://github.com/hayashi-ay/leetcode/pull/28/ | ||
| * 個人的に、PythonやJavascriptのMapのkeyの入力順が保存されることを使用した | ||
| 方法(`*04`)が読みやすくて好み。 | ||
| https://github.com/hayashi-ay/leetcode/pull/28/files#diff-5ec7c3c87171edf4d61e9eb79fd926cafa27caf068da7474222897c8e9e7ab96R91-R107 | ||
|
|
||
| * hroc135 | ||
| * PR: https://github.com/hroc135/leetcode/pull/15/ | ||
| * 「どの情報から順番に出してあげることで、より早く伝わるかを考える。 」をすぐに取り入れたい。 | ||
| https://github.com/hroc135/leetcode/pull/15/files#r1731514545 | ||
| * メモリにおけるキャッシュ効率について理解ができていないので、勉強する。 | ||
|
|
||
| * kazukiii | ||
| * PR: https://github.com/kazukiii/leetcode/pull/16/files | ||
| * nodchipさんのコメント | ||
| * 以下を理解できていないので、勉強する。 | ||
|
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. 本を読み、nodchipさんとodaさんの指摘が理解できました。 プロセッサの記憶階層は、以下のようになっている。
値が8バイト以下に収まるデータを考えた際に、
コンパイラの中で、以下のような最適化が行われる。 だから、参照渡しでも値渡しと同等の性能が得られることがある。
(プロセッサを支える技術の3章の「キャッシュの仕組み」から「プロセッサの記憶階層」 を参照しました。) 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. ありがとうございます! |
||
| > x64 アーキテクチャーを仮定するのであれば、値が 64 ビット (8 バイト) 以内に収まる場合は、 | ||
| > 参照にしないほうが良いと思います。理由は、値が 64 ビット (8 バイト) 以内に収まる場合、 | ||
| > 汎用レジスター 1 本に格納できるためです。 | ||
|
|
||
| ## その他の方法 | ||
|
|
||
| * `*01` BruteForceアプローチ | ||
|
|
||
| ```javascript | ||
| const firstUniqChar = function(s) { | ||
| for (let i = 0; i < s.length; i++) { | ||
| let isIthCharUnique = true | ||
| for (let j = 0; j < s.length; j++) { | ||
| if (i !== j && s[i] === s[j]) { | ||
| isIthCharUnique = false | ||
| break | ||
| } | ||
| } | ||
| if (isIthCharUnique) { | ||
| return i | ||
| } | ||
| } | ||
| return -1 | ||
| }; | ||
| ``` | ||
|
|
||
| * `*02` キューを用いた方法 | ||
| * 参照 :野田さんのコメント https://github.com/colorbox/leetcode/pull/29/files#r1861430039 | ||
|
|
||
| ```javascript | ||
| const firstUniqChar = function(s) { | ||
| const container = [] | ||
| const charToCount = new Map() | ||
| for (let i = 0; i < s.length; i++) { | ||
| const pair = new Pair(i, s[i]) | ||
| container.push(pair) | ||
| const count = charToCount.get(s[i]) || 0 | ||
| charToCount.set(s[i], count + 1) | ||
|
|
||
| let top = container[0] | ||
| while (container.length > 0 && charToCount.get(top.char) > 1){ | ||
| container.shift() | ||
| top = container[0] | ||
| } | ||
| } | ||
| if (container.length > 0) { | ||
| return container[0].index | ||
| } | ||
| return -1 | ||
| }; | ||
| ``` | ||
|
|
||
| * `*03` 配列を用いた方法 | ||
|
|
||
| ```javascript | ||
| const firstUniqChar = function(s) { | ||
| const charCount = new Array(26).fill(0) | ||
| const aAsciiCode = 'a'.charCodeAt(0) | ||
| const zAsciiCode = 'z'.charCodeAt(0) | ||
| for (const c of s) { | ||
| const ithCharAsciiCode = c.charCodeAt(0) | ||
| if (ithCharAsciiCode < aAsciiCode && zAsciiCode < ithCharAsciiCode) { | ||
| throw new Error('invalid input') | ||
| } | ||
| const idx = ithCharAsciiCode - aAsciiCode | ||
| charCount[idx]++ | ||
| } | ||
| for (let i = 0; i < s.length; i++) { | ||
| const idx = s[i].charCodeAt(0) - aAsciiCode | ||
| if (charCount[idx] === 1) { | ||
| return i | ||
| } | ||
| } | ||
| return -1 | ||
| }; | ||
| ``` | ||
|
|
||
| * `*04` JavaScriptのMapが順序を保持することを利用した方法 | ||
|
|
||
| ```javascript | ||
| const firstUniqChar = function(s) { | ||
| const duplicated = new Set() | ||
| const uniqueCharToIdx = new Map() | ||
| for (let i = 0; i < s.length; i++) { | ||
| if (duplicated.has(s[i])) { | ||
| continue | ||
| } | ||
| if (uniqueCharToIdx.has(s[i])) { | ||
| uniqueCharToIdx.delete(s[i]) | ||
| duplicated.add(s[i]) | ||
| continue | ||
| } | ||
| uniqueCharToIdx.set(s[i], i) | ||
| } | ||
| if (uniqueCharToIdx.size === 0) { | ||
| return -1 | ||
| } | ||
| const iterator = uniqueCharToIdx.values() | ||
| return iterator.next().value | ||
| }; | ||
| ``` | ||
|
|
||
| ### コードの良し悪し | ||
|
|
||
| * `*10` 出現回数を数えて、1回だけ出現するものを見つけた際に、そのインデックスを返す方法 | ||
| * 空間計算量 : O(1) | ||
| * 時間計算量 : O(N) for文が2周している。 | ||
|
|
||
| * `*11` Brute Forceアプローチ | ||
| * 空間計算量 : O(1) | ||
| * 時間計算量 : O(N^2) | ||
|
|
||
| * `*12` キューを用いた方法 | ||
| * 空間計算量 : O(N) キューを用いている。 | ||
| * 時間計算量 : O(N) for文が1周している。 | ||
|
|
||
| * `*13` 配列を用いた方法 | ||
| * 空間計算量 : O(1) | ||
| * 時間計算量 : O(N) for文が2周している。 | ||
|
|
||
| * `*14` JavaScriptのMapが順序を保持することを利用した方法 | ||
| * 空間計算量 : O(N) duplicatedのSetで、O(N)の空間計算量が必要。 | ||
| * 時間計算量 : O(N) for文が1周している。 | ||
|
|
||
| ## 調べたこと | ||
|
|
||
| * str.encode関数は、UTF-8にエンコードされる. | ||
| https://docs.python.org/3/library/stdtypes.html#str.encode | ||
|
|
||
| * String.prototype.charCodeAt() https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt | ||
|
|
||
| > charCodeAt() always indexes the string as a sequence of UTF-16 code units, | ||
| > so it may return lone surrogates. To get the full Unicode code point | ||
| > at the given index, use String.prototype.codePointAt(). | ||
|
|
||
| ```javascript | ||
| const c = "𩸽"; | ||
|
|
||
| console.log(c.length); // 2 (2つのコードユニットで構成) | ||
| console.log(c.charCodeAt(0)); // 55399 (高位サロゲート) | ||
| console.log(c.charCodeAt(1)); // 56893 (低位サロゲート) | ||
| console.log(c.codePointAt(0)) // 171581 (UniCodeコードポイント) | ||
|
|
||
| const codePoint = c.codePointAt(0) | ||
| // codePoint = (highSurrogate - 0xD800) * 0x400 + (lowSurrogate - 0xDC00) + 0x10000 | ||
| highSurrogate = Math.floor((codePoint - 0x10000) / 0x400) + 0xD800 // 55399 | ||
| lowSurrogate = ((codePoint - 0x10000) % 0x400) + 0xDC00 // 56893 | ||
| console.log(highSurrogate === c.charCodeAt(0)) // true | ||
| console.log(lowSurrogate === c.charCodeAt(1)) // true | ||
| ``` | ||
|
|
||
| * ord() は、Unicode CodePointが返却される。 | ||
| https://docs.python.org/3/library/functions.html#ord | ||
|
|
||
| * PythonのOrderedDictは、双方向連結リストとHash Tableでできている。 | ||
| https://github.com/python/cpython/blob/a66bae8bb52721ea597ade6222f83876f9e939ba/Lib/collections/__init__.py#L89 | ||
|
|
||
| * C++のmapは、keyでソートされている。 | ||
| https://cplusplus.com/reference/map/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. std::mapは平衡二分木で,pythonのdictやJSのMapに対応するハッシュテーブルベースのデータ構造としてはstd::unordered_mapがあります. |
||
|
|
||
| * Pythonのcollections.Counterの内部実装 | ||
| * https://github.com/python/cpython/blob/a66bae8bb52721ea597ade6222f83876f9e939ba/Lib/collections/__init__.py#L551 | ||
| * Pure Pythonで書かれている。 | ||
|
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_elements についてはCの実装があれば使われるようですね。 |
||
|
|
||
| * Goにおけるmapの内部実装 | ||
| https://github.com/golang/go/blob/eb4069127a7dbdaed480aed80ba6ed1b2ea27901/src/internal/runtime/maps/map.go | ||
|
|
||
| * ハッシュ表の理解 (kumagiさんのスライド) | ||
| * OpenAddressing, ClosedAddressingのところまで読んだ。 | ||
| https://www.docswell.com/s/kumagi/ZGXXRJ-hash-table-world-which-you-dont-know | ||
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.
読みやすいです。個人的にはcountは切り出さずにsetのカッコの中にそのまま入れてしまってもいい気がします。