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
118 changes: 118 additions & 0 deletions 0283-move-zero/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# 283. Move Zeroes
- 受け取ったint配列のなかのすべてのゼロを配列の後ろに移動させる。
- 非ゼロの数はそのままの順番を保つ。また、配列のコピーなどはつくらずin-placeでやること。

## Step1

- 手作業で考える
- 大人と子供がランダムに並んでいて、大人たちを後ろにやりたい=子どもたちを前にやりたい。子供の順番は保ったまま。
- 列の左右に担当者AとBを置いて、
- Aは先頭から普通にひとりずつ進んでいく。Bは最初、先頭で止まっておく。
- Aが子供を見つけたら、Bの位置の人とスワップしてもらう。スワップしたときだけBもひとりぶん進む。
- Bはそれまで確定させた子供たちの次の位置に常にいるようになるはず。
- 大人が続くほどAとBの間が開いていくイメージ。

- 失敗
```python
class Solution:
def moveZeroes(self, nums: list[int]) -> None:
end_of_nonzeroes = 0

for num in nums:
if num != 0:
tmp = nums[end_of_nonzeroes]
nums[end_of_nonzeroes] = num
num = tmp
end_of_nonzeroes += 1
```

- for num in nums: の num は、配列の要素のコピー(要素への参照を受け取ったローカル変数)であって、配列の場所そのものではない
- num = xとしても、ループの中で捨てられていくローカル変数に代入しているだけ。
- この時点では動かないのでwhileにしようという意識でつぎへ。


```python
class Solution:
def moveZeroes(self, nums: list[int]) -> None:
i = 0
end_of_nonzeroes = 0

while i < len(nums):
if nums[i] != 0:
tmp = nums[end_of_nonzeroes]
nums[end_of_nonzeroes] = nums[i]
nums[i] = tmp

end_of_nonzeroes += 1
i += 1
```

- 計算量
- 時間O(n)
- 空間O(1) ∵インデックスとtmpの保持のみ


## Step2
- AI
- > whileよりforのほうがよい

最初にforで動かなかった+42で初期はfor禁止だった。
失敗の原因はわかり、特にこだわることもないので、forを使う。
- > swapにtmpは不要

`nums[end_of_nonzeroes], nums[i] = nums[i], nums[end_of_nonzeroes]`Pythonではこちらが定番。Cの学習内容で書いたtmpだったので以後改善する。
- > Two Pointerが同じ場所を指すときswapは不要

インデントが一段深くなるけれど、最適化と明示化のために悪くないと思った。ただ、スワップが減るぶん、`if i != end_of_nonzeroes`の比較が毎回走る。そこまでは変わらないんじゃないか。

- [先人1](https://github.com/Manato110/LeetCode-arai60/pull/55/changes)
- `in-place`とは、
> 引数で受け取ったオブジェクトに対して変更を加えて目的を達成すること

> in-placeではない場合は、引数で受けとったものは不変で、返り値として値を返すことで目的を達成

> それに加えて空間計算量が入力サイズに比例しないこと

べつで配列を用意したりしてはいけないものだと思っていた。正直最初は非ゼロをべつに避けて、もとの配列の頭にそれを詰める+もとの長さマイナス詰めたぶんのゼロで埋める、がぱっと浮かんだのだけれど、それはin-placeではないかと思い捨てた。ただ、それだと空間計算量がO(n)になるので最後の条件でアウトか。

- せっかくなので書いてみる(Step1でやるべきことでしたが)
```python
class Solution:
def moveZeroes(self, nums: list[int]) -> None:
non_zeroes = []

for num in nums:
if num != 0:
non_zeroes.append(num)

i = 0
for non_zero in non_zeroes:
nums[i] = non_zero
i += 1

while i < len(nums):
nums[i] = 0
i += 1
```
- 空間計算量はTwo Pointers方式とLeetcode上では変わらなかった、なぜだろう?あまり信頼できない指標とは聞くが...

- [先人2](https://github.com/fhiyo/leetcode/pull/54/changes/BASE..40f6172e4c7a6b29303a6b66464dd512300ac477#diff-2f8b85074aa38861aa9dd6fbe0c5f1b540a06f8618d7552b4ffd05da21f795d3R138)
- `if nums[i] == 0: continue`で弾いてしまえば、本処理のインデントを一段浅くできる。


- ブラッシュアップ
```python
class Solution:
def moveZeroes(self, nums: list[int]) -> None:
end_of_nonzeroes = 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

この変数は、実際には「次にゼロ以外を置くべき位置」を表しているので、next_non_zero_indexなどが命名として良さそうだと思いました

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.

そうですね、名前は悩みました。
たしかにnext_non_zero_indexのほうがわかりやすいかと思います!


for i, num in enumerate(nums):
if num == 0:
continue

nums[end_of_nonzeroes], nums[i] = nums[i], nums[end_of_nonzeroes]
end_of_nonzeroes += 1
```

## Step3
- 上記ブラッシュアップのコードを三回再現。
25 changes: 25 additions & 0 deletions 0283-move-zero/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class Solution:
def moveZeroes(self, nums: list[int]) -> None:
i = 0
end_of_nonzeroes = 0

while i < len(nums):
if nums[i] != 0:
tmp = nums[end_of_nonzeroes]
nums[end_of_nonzeroes] = nums[i]
nums[i] = tmp

end_of_nonzeroes += 1
i += 1


def main() -> None:
Solver = Solution()
nums = [0,1,0,3,12]
print(f"before: {nums}")
Solver.moveZeroes(nums)
print(f"after: {nums}")


if __name__ == "__main__":
main()
22 changes: 22 additions & 0 deletions 0283-move-zero/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class Solution:
def moveZeroes(self, nums: list[int]) -> None:
end_of_nonzeroes = 0

for i, num in enumerate(nums):
if num == 0:
continue

nums[end_of_nonzeroes], nums[i] = nums[i], nums[end_of_nonzeroes]
end_of_nonzeroes += 1


def main() -> None:
Solver = Solution()
nums = [0,1,0,3,12]
print(f"before: {nums}")
Solver.moveZeroes(nums)
print(f"after: {nums}")


if __name__ == "__main__":
main()
69 changes: 69 additions & 0 deletions 0387-first-unique-char-in-str/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# 387. First Unique Character in a String

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 つの PR に含める回答は、問題 1 つ分にすることをお勧めいたします。 2 つ以上の問題が 1 つの PR に含まれていると、レビューの対象が分散し、レビューの品質が下がる可能性があります。

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.

ブランチの切り方を間違えたためかと思われますので、以後注意いたします。


## Step 1
- 一回文字列を頭から舐めて文字:出現数で辞書に登録
- もう一度文字列を走査して、出現数が1の文字のインデックス(かなければ-1)を返す
- 時間、空間ともにO(n)
- 入力が<= 10^5であることを考えるとPythonでも0.1秒収まるくらい?(平均的な処理能力を10^8/sec、Pythonがそこから最悪100倍で見積もっています、初めてなので勘違いしている点やリファレンスがあればコメントください)

```python
class Solution:
def firstUniqChar(self, s: str) -> int:
appearences: dict[str, int] = {}

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 の変数名には、キーと値がそれぞれどのようなものを表すかが分かる名前を付けると良いと思います。また、しばしば (キー)_to_(値) という書式が使われるように思います。 char_to_count はいかがでしょうか?

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.

dict の変数名には、キーと値がそれぞれどのようなものを表すかが分かる名前を付けると良い

以後意識いたします。


for c in s:
if c in appearences:
appearences[c] += 1
else:
appearences[c] = 1

for c in s:
if appearences[c] == 1:
return s.index(c)

return -1
```

## Step 2
- enumerate()を使っている人が多かった。str.index()は文字列を走査し直す(当然でした)のでここで最悪O(n^2)になっている。
- collections.Counter()という便利なやつがいる。
```python
from collections import Counter


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

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

return -1
```

- OrderDictという要素の順番を保持してくれるものがある。辞書の値に出現回数ではなく最初に見つけたインデックスを保存し、二回目以降の出現があればそれを潰しておく。二回目のループのとき、辞書は文字列に登場した順番通りになっているので、潰れていないインデックスが出た瞬間それを返して問題ない。
- Python3.7以降ふつうのdictも追加順を保持しているらしく(Dictionaries preserve insertion order.(https://docs.python.org/3/library/stdtypes.html#mapping-types-dict))、そのままdictで実装した。OrderDictをインポートしたほうがいいんでしょうか?
- 2回目のループが最長26回で済むのでだいぶ効率的。

```python
class Solution2:
def firstUniqChar(self, s: str) -> int:
seen_index:dict[str, int] = {}
duplicated = -1

for i, c in enumerate(s):
if c in seen_index:
seen_index[c] = duplicated
else:
seen_index[c] = i

for idx in seen_index.values():
if idx != duplicated:
return idx

return -1
```

## Step 3
- OrderDictのほうが効率的かつ明示的だと思ったのでそちらで三回実装。
26 changes: 26 additions & 0 deletions 0387-first-unique-char-in-str/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class Solution:
def firstUniqChar(self, s: str) -> int:
appearences: dict[str, int] = {}

for c in s:
if c in appearences:
appearences[c] += 1
else:
appearences[c] = 1

for c in s:
if appearences[c] == 1:
return s.index(c)

return -1


def main() -> None:
Solver = Solution()
s = "loveleetcode"
res = Solver.firstUniqChar(s)
print(res)


if __name__ == "__main__":
main()
30 changes: 30 additions & 0 deletions 0387-first-unique-char-in-str/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from collections import Counter


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

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

return -1


class Solution2:
def firstUniqChar(self, s: str) -> int:
seen_index:dict[str, int] = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

: のあとにスペースを空けるものと空けないものとが混ざっているのが気になりました。空けるほうに統一することをお勧めいたします。

duplicated = -1

for i, c in enumerate(s):
if c in seen_index:
seen_index[c] = duplicated
else:
seen_index[c] = i

for idx in seen_index.values():
if idx != duplicated:
return idx

return -1