-
Notifications
You must be signed in to change notification settings - Fork 0
0283 move zeroes #5
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
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,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 | ||
|
|
||
| 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 | ||
| - 上記ブラッシュアップのコードを三回再現。 | ||
| 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() |
| 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() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| # 387. First Unique Character in a String | ||
|
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 つの PR に含める回答は、問題 1 つ分にすることをお勧めいたします。 2 つ以上の問題が 1 つの PR に含まれていると、レビューの対象が分散し、レビューの品質が下がる可能性があります。
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. ブランチの切り方を間違えたためかと思われますので、以後注意いたします。 |
||
|
|
||
| ## 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] = {} | ||
|
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. dict の変数名には、キーと値がそれぞれどのようなものを表すかが分かる名前を付けると良いと思います。また、しばしば (キー)_to_(値) という書式が使われるように思います。 char_to_count はいかがでしょうか?
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.
以後意識いたします。 |
||
|
|
||
| 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のほうが効率的かつ明示的だと思ったのでそちらで三回実装。 | ||
| 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() |
| 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] = {} | ||
|
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.
|
||
| 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 | ||
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.
この変数は、実際には「次にゼロ以外を置くべき位置」を表しているので、
next_non_zero_indexなどが命名として良さそうだと思いました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.
そうですね、名前は悩みました。
たしかにnext_non_zero_indexのほうがわかりやすいかと思います!