Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions problems/387.first-unique-character-in-a-string/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## step1
- charactorがsの中に入っているかを愚直に見ていくのは?
- 10^5の場合setにしても大丈夫な大きさなはず
- 文字列が含んでいるかのinの判定は早いと聞いたことがあるが、内部の実装まで知らないのでstep2で調べたい
- 計算量はO(N^2)
- Counterの方が確実かもしれない
- Counterの場合はO(N)
- 見つからなかった場合の-1は定数(NO_UNIQUE_VALUE_RESPONSEのような感じ?)に入れておくべきか迷った
- そもそも-1を返すのはやめたい気持ちがある(Noneの方が後々indexとして使おうとするとエラーになるのでいいのではないかと思っている)

## step2
- chaが組み込み関数なのでcとしたが、charの方が良かったかもしれない
- 他の人のコードをみる
- https://github.com/Hiroto-Iizuka/coding_practice/pull/15/files
- `この問題は LRU に類似したデータ構造を私は連想しましたが、しなくてもいいかの境界くらいです。よく聞かれるので知っていてもいいかもしれません。(よく聞かれる理由は知らなくても作れていいだろうと思われるからですね。)`
- https://github.com/mamo3gr/arai60/pull/15/files
- `next`や`iter`に馴染みが無かったので調べる
- https://docs.python.org/ja/3/library/functions.html#next
- https://docs.python.org/ja/3/library/functions.html#iter
- こういうのを使っていると上級者なんだろうなと思う
- 自分から書く選択肢に全くないのでなんとかしたい気持ち
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

私も知らなくて、調べて見つけた記憶があります。
dict.values() の一番目だけ欲しいけど、dict[1] とは書けないし、リストにしたら全要素コピーされて無駄だぞ…。何か方法があるんじゃないかな、と思って調べて突き当たりました。
私の経験からですが「何か方法があるんじゃないかな」は大抵の場合、方法があります。

- https://github.com/yas-2023/leetcode_arai60/pull/15/files
- ベンチマークすごい
- 自分もloopの際に省略せずindex, characterと書いた方が良いかも
- inを使うよりもfindとrfindの方がネイティブで実行されるため良いのか?
- https://docs.python.org/ja/3/library/stdtypes.html#str.find
- `注釈 find() メソッドは、 sub の位置を知りたいときにのみ使うべきです。 sub が部分文字列であるかどうかのみを調べるには、 in 演算子を使ってください:`
- inは内部では`文字列やバイト列型については、 x in y は x が y の部分文字列であるとき、かつそのときに限り True になります。これは y.find(x) != -1 と等価です。空文字列は、他の任意の文字列の部分文字列とみなされます。従って "" in "abc" は True を返すことになります。`なので速度的には変わらないみたい
- `この方法ですと、入力が"aaaaaa....aab"みたいな時にforループが何度もaをチェックしてしまうため、個人的にはStep 1のように文字ごとにfirst indexを覚えておく方が好ましいと思います。計算量は同じO(n)ですが、場合によっては2倍ほどになる気がします。`
- counter作るのでO(N)でloopで0(N)なので実質2回forのloopをまわしているが、2回目は種類が少ない文字列側で見ていくと確かに良さそうだと思った
- https://github.com/Yuto729/LeetCode_arai60/pull/20/files
- 上記のコメントで出てきたLRUライクの意味が実装を見てわかった
- 2回目に出てきた時にdictのkeyを消して、あとはseenにあるかを見て最後に一番indexが早いものからdictに追加されたはずなので.values()で取り出す流れ
- Counterも追加した順番が保持されるのかは気になったので後で調べる
- `Counter は ハッシュ可能 なオブジェクトをカウントする dict のサブクラスです。これは、要素を辞書のキーとして保存し、そのカウントを辞書の値として保存するコレクションです。カウントは、0 や負のカウントを含む整数値をとれます。 Counter クラスは、他の言語のバッグや多重集合のようなものです。`
- https://docs.python.org/ja/3/library/collections.html#collections.Counter

- step2に1回だけ走査する方法(1passというらしい)
- O(N)
- step2-2にCounterで辞書の入力順を利用した文字列側でのloopで解く方法を書いてみる
- カウンター作成でO(N), 文字のloopでO(M), findでO(N) Mは文字の種類

## step3
- 1passの方法とカウンターの方法で2回ずつ書いてみる
- カウンターの方法の方が見た時に何しているか理解するのに時間がかからない気がする
- step2の時に他の人のコード見て思ったが、誰も見つからなかった時の-1を定数に入れたりしてなかったのでしないのが普通の感覚なのかと思った
- 自分も普段は絶対しないと思うが、いきなり−1が出てきて減点されないかどうかが少し心配だった

21 changes: 21 additions & 0 deletions problems/387.first-unique-character-in-a-string/step1-2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#
# @lc app=leetcode id=387 lang=python3
#
# [387] First Unique Character in a String
#

# @lc code=start
import collections


class Solution:
def firstUniqChar(self, s: str) -> int:
charactor_counter = collections.Counter(s)
for i, c in enumerate(s):
if charactor_counter[c] == 1:
return i

return -1


# @lc code=end
17 changes: 17 additions & 0 deletions problems/387.first-unique-character-in-a-string/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# @lc app=leetcode id=387 lang=python3
#
# [387] First Unique Character in a String
#

# @lc code=start
class Solution:
def firstUniqChar(self, s: str) -> int:
for i, c in enumerate(s):
if c not in s[:i] and c not in s[i + 1 :]:
return i

return -1


# @lc code=end
22 changes: 22 additions & 0 deletions problems/387.first-unique-character-in-a-string/step2-2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#
# @lc app=leetcode id=387 lang=python3
#
# [387] First Unique Character in a String
#

# @lc code=start
import collections


class Solution:
def firstUniqChar(self, s: str) -> int:
charactor_counter = collections.Counter(s)

for charactor, count in charactor_counter.items():
if count == 1:
return s.find(charactor)

return -1


# @lc code=end
32 changes: 32 additions & 0 deletions problems/387.first-unique-character-in-a-string/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#
# @lc app=leetcode id=387 lang=python3
#
# [387] First Unique Character in a String
#

# @lc code=start


class Solution:
def firstUniqChar(self, s: str) -> int:
charactor_to_index = dict()
seen_charactors = set()

for index, charactor in enumerate(s):
if charactor in charactor_to_index:
del charactor_to_index[charactor]
continue

if charactor in seen_charactors:
continue

charactor_to_index[charactor] = index
seen_charactors.add(charactor)

if charactor_to_index:
return next(iter(charactor_to_index.values()))

return -1


# @lc code=end
30 changes: 30 additions & 0 deletions problems/387.first-unique-character-in-a-string/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#
# @lc app=leetcode id=387 lang=python3
#
# [387] First Unique Character in a String
#


# @lc code=start
class Solution:
def firstUniqChar(self, s: str) -> int:
charactor_to_index = dict()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

タイポしてますね。

Suggested change
charactor_to_index = dict()
character_to_index = dict()

seen_charactors = set()

for index, charactor in enumerate(s):
if charactor in charactor_to_index:
del charactor_to_index[charactor]
continue
Comment on lines +15 to +17
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

頭から読んだとき、

  • なぜ辞書でヒットしたら、そこからインデックスを消しちゃうんだ?seen_characters に含まれているかの確認は先にしなくていいのか?
    • 正解は、ユニークな文字だけ を辞書に保存したいから
    • seen_characters には add しなくていいのか?
      • 実は、辞書に追加されているなら seen_characters にも入っている(これは後ろを読んで分かる)

のように、実際の動きを頭の中で頑張ってシミュレートする必要があり、短い行数のわりに大変に感じました。

if charactor in seen_charactors:
continue

charactor_to_index[charactor] = index
seen_charactors.add(charactor)

if charactor_to_index:
return next(iter(charactor_to_index.values()))

return -1


# @lc code=end