Skip to content
Merged
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
164 changes: 164 additions & 0 deletions 競技プロ就活部PR用/387. First Unique Character in a String.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
> Note
> python dict型におけるpop, delの挙動に関して
> (TORUS0818さんのPR: https://github.com/TORUS0818/leetcode/pull/17)
```
https://docs.python.org/3/library/stdtypes.html#dict.pop
どちらも存在しない値を指定するとKeyError(ただ今回if文でチェックしているから今回は関係ない)
popは存在しない値だった場合、KeyErrorを出さずに第二引数で指定したdefault値を返せる e.g.) sample_dict.pop('b', 'default_value')
popは削除したkeyの値を返す、delは消すだけ
del文は複数削除可能 (これはlistのスライスに対してのコメントかも。)
そのほか、最後に追加されたkeyとvalueをどちらも返すmy_dict.popitem()、すべての要素を削除するdict.clear()がある。
```

```python
test_dict = {1: 1, 2: 4, 3: 9, 4: 16}

## del O(1)
del test_dict[1]
print(test_dict) # {2: 4, 3: 9, 4: 16}

## pop O(1)
val = test_dict.pop(4, "デフォルト")
print(val, test_dict) # 16 {2: 4, 3: 9}
val = test_dict.pop(4, "デフォルト")
print(val) # デフォルト

# popitem O(1)
key, val = test_dict.popitem()
print(key, val) # 3 9

# clear O(N)
test_dict.clear()
print(test_dict) # {}
```


## Dictによる解法
時間計算量: O(N)<br>
空間計算量: O(N)<br>

### 1回目 (10m33s)
```python
class Solution:
def firstUniqChar(self, s: str) -> int:
letter_frequency_counter = defaultdict(int)
for c in s:
letter_frequency_counter[c] += 1

for i, c in enumerate(s):
if letter_frequency_counter[c] == 1:
return i
return -1
```

### 2回目
> letter_frequency_counterが少し冗長なため、char_counterに変更。
> defaultdictではなく、dictを利用して、char_counter[c] = char_counter.get(c, 0) + 1でも良いかも。
```python
class Solution:
def firstUniqChar(self, s: str) -> int:
char_counter = defaultdict(int)
for c in s:
char_counter[c] += 1

for i, c in enumerate(s):
if char_counter[c] == 1:
return i
return -1
```

### 3回目
> 2回目から変更なし。
```python
class Solution:
def firstUniqChar(self, s: str) -> int:
char_counter = defaultdict(int)
for c in s:
char_counter[c] += 1

for i, c in enumerate(s):
if char_counter[c] == 1:
return i
return -1
```


## ヒストグラム化を利用した解法

時間計算量: O(N)<br>
空間計算量: O(N)<br>

```python
class Solution:
def firstUniqChar(self, s: str) -> int:
char_histogram = [0] * (ord('z') - ord('a') + 1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

char_histogram は英小文字の差しか見れないと思うので、alphabet_histogram とかでもいいかもしれません。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@Yoshiki-Iwasa さん、 @goto-untrapped さん レビューコメントありがとうございます。
問題文の制約で、lower case English lettersのためこの書き方にしていますが、それ以外の入力があった場合はエラー処理を書くか、コメントがあったほうが親切ですね。また、list範囲外のindexは、pythonではIndexError: list index out of rangeが発生します。負の値もindexに使えますが、末尾から値を取る形になります。(ちょっと公式ドキュメントが見当たりませんでした。https://qiita.com/mo256man/items/ed8ea21bd0ae94e5ed48)

また、@goto-untrappedさんからコメントいただいた通り、変数名が良くない(広い)ので、alphabet_histogramの方が良いですね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

oda さんか誰かが、英語圏の人が同僚になる想定で面接を受けましょうと言っていた気がします。英語圏の人にとってアルファベットが26文字なのは自明のはずなので、単に26を渡して配列を宣言してもいいのかなと思いました。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

すいません、投稿後すぐ考え直したのですが、翻って自分がひらがなの数をすぐ答えられるかと問われたら全然自信が無いなと思ったので、やはりこれで良いかも知れません...🫠
とはいえ、むしろアルファベットが26文字というのが自明でないのなら ord('z') - ord('a') + 1 の結果が何になるのかもすぐには分からないはずなので、例えば ALPHABET_NUMBER のような定数を宣言し、26 代入して使ってあげるとより親切になるかな、とも思いました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

コメント有難うございます。そもそもちょっと練習のためにこの書き方を採用したというのが一番大きな理由になってしまうんですが、ALPHABET_NUMBER = 26が一番自分もわかりやすいですし、メンテナンスしやすいですね。

for c in s:
char_histogram[ord(c) - ord('a')] += 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@Mike0121
仮に英小文字以外の文字が入っていたらどうなるんでしょうか?

言語にもよりますが、リストへのindexアクセスは容易にクラッシュを引き起こすので気になりました。(確かPythonは負のindexを使えるんでしたっけ?)

もし考慮済みだったら一言コメントがあるといいかもしれません


for i, c in enumerate(s):
if char_histogram[ord(c) - ord('a')] == 1:
return i

return -1
```


## 1-passの解法
> for文自体は確かに1-passであるが、return char_to_index.values()で
> 結局はchar_to_indexに格納された値を操作している点が少し気になった。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

あと min() が O(n) の操作をしているのも気になりますね...


Copy link
Copy Markdown
Owner Author

@Mike0121 Mike0121 Jun 22, 2024

Choose a reason for hiding this comment

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

こちら下記でOdaさんが既に指摘済みでした。
TORUS0818/leetcode#17

時間計算量: O(N)<br>
空間計算量: O(N)<br>

```python
class Solution:
def firstUniqChar(self, s: str) -> int:
non_unique_chars = set()
char_to_index = {}

for i, c in enumerate(s):
if c in non_unique_chars:
continue

if c in char_to_index:
del char_to_index[c]
non_unique_chars.add(c)
continue
char_to_index[c] = i
if not char_to_index:
return -1

return min(char_to_index.values())
```

## rindex, indexを利用した解法
https://docs.python.org/3/library/stdtypes.html#str.index
https://docs.python.org/3/library/stdtypes.html#str.index

時間計算量: O(N^2)<br>
空間計算量: O(1)<br>
```python
class Solution:
def firstUniqChar(self, s: str) -> int:
for c in s:
if s.rindex(c) == s.index(c):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

index は start の引数が使えるので、頭から尻尾まで見ない手もありますね。
s.index(c, i + 1) など。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Odaさん、ありがとうございます。
enumerateにして、s.index(c, i + 1) == -1にすれば、少し効率的にできますね。
また、rindexを使わなくて良くなるので、indexさえ理解してれば伝わるのも良いと思いました。
頭に入れておきます。

return s.index(c)
return -1
```

## Counterを使った解法
> https://docs.python.org/3/library/collections.html#collections.Counter
> カウンタオブジェクトは辞書のインターフェースを持ちますが、存在しない要素に対して KeyError を送出する代わりに 0 を返すという違いがあります
> defaultdict(int)と同じ。
時間計算量: O(N)<br>
空間計算量: O(N)<br>

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