From 1a45bb32134d2ed017fd2dcc68fd1c9a73b2b661 Mon Sep 17 00:00:00 2001 From: Tomoki Sadamori Date: Sat, 18 Jul 2026 14:47:22 +0900 Subject: [PATCH 1/2] =?UTF-8?q?step3=E3=81=BE=E3=81=A7=E5=AE=8C=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 0387-first-unique-char-in-str/memo.md | 69 ++++++++++++++++++++++++++ 0387-first-unique-char-in-str/step1.py | 26 ++++++++++ 0387-first-unique-char-in-str/step2.py | 30 +++++++++++ 3 files changed, 125 insertions(+) create mode 100644 0387-first-unique-char-in-str/memo.md create mode 100644 0387-first-unique-char-in-str/step1.py create mode 100644 0387-first-unique-char-in-str/step2.py diff --git a/0387-first-unique-char-in-str/memo.md b/0387-first-unique-char-in-str/memo.md new file mode 100644 index 0000000..48dbab3 --- /dev/null +++ b/0387-first-unique-char-in-str/memo.md @@ -0,0 +1,69 @@ +# 387. First Unique Character in a String + +## 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] = {} + + 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のほうが効率的かつ明示的だと思ったのでそちらで三回実装。 \ No newline at end of file diff --git a/0387-first-unique-char-in-str/step1.py b/0387-first-unique-char-in-str/step1.py new file mode 100644 index 0000000..46eb82e --- /dev/null +++ b/0387-first-unique-char-in-str/step1.py @@ -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() \ No newline at end of file diff --git a/0387-first-unique-char-in-str/step2.py b/0387-first-unique-char-in-str/step2.py new file mode 100644 index 0000000..e79b0c3 --- /dev/null +++ b/0387-first-unique-char-in-str/step2.py @@ -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] = {} + 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 From 3af739266cfef74f3ddfd1993223c9d02c540224 Mon Sep 17 00:00:00 2001 From: Tomoki Sadamori Date: Wed, 22 Jul 2026 17:54:31 +0900 Subject: [PATCH 2/2] done --- 0283-move-zero/memo.md | 118 ++++++++++++++++++++++++++++++++++++++++ 0283-move-zero/step1.py | 25 +++++++++ 0283-move-zero/step2.py | 22 ++++++++ 3 files changed, 165 insertions(+) create mode 100644 0283-move-zero/memo.md create mode 100644 0283-move-zero/step1.py create mode 100644 0283-move-zero/step2.py diff --git a/0283-move-zero/memo.md b/0283-move-zero/memo.md new file mode 100644 index 0000000..a2d5ef0 --- /dev/null +++ b/0283-move-zero/memo.md @@ -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 +- 上記ブラッシュアップのコードを三回再現。 \ No newline at end of file diff --git a/0283-move-zero/step1.py b/0283-move-zero/step1.py new file mode 100644 index 0000000..da23386 --- /dev/null +++ b/0283-move-zero/step1.py @@ -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() diff --git a/0283-move-zero/step2.py b/0283-move-zero/step2.py new file mode 100644 index 0000000..307f4b8 --- /dev/null +++ b/0283-move-zero/step2.py @@ -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() \ No newline at end of file