From 4958734fe4a7be8b5945134a851b988192fc4ec7 Mon Sep 17 00:00:00 2001 From: t9a Date: Thu, 26 Feb 2026 08:34:40 +0900 Subject: [PATCH] solve: 283.Move Zeroes --- src/bin/step1.rs | 74 ++++++++++++++++++++++++++++++++++++ src/bin/step2.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++ src/bin/step2a.rs | 59 +++++++++++++++++++++++++++++ src/bin/step3.rs | 53 ++++++++++++++++++++++++++ 4 files changed, 281 insertions(+) create mode 100644 src/bin/step1.rs create mode 100644 src/bin/step2.rs create mode 100644 src/bin/step2a.rs create mode 100644 src/bin/step3.rs diff --git a/src/bin/step1.rs b/src/bin/step1.rs new file mode 100644 index 0000000..d65382b --- /dev/null +++ b/src/bin/step1.rs @@ -0,0 +1,74 @@ +// Step1 +// 目的: 方法を思いつく + +// 方法 +// 5分考えてわからなかったら答えをみる +// 答えを見て理解したと思ったら全部消して答えを隠して書く +// 5分筆が止まったらもう一回みて全部消す +// 正解したら終わり + +/* + 問題の理解 + - 整数からなる配列numsが与えられる。numsに含まれる0を配列の末尾に移動する。このとき、0以外の数値は並び順を変更しない。 + 新たに配列を作らずに(numsをコピーせず)処理を行う必要がある。 + + 何を考えて解いていたか + - 配列を走査しながら0を見つけたらremove()して、push()すればin-placeで解けそう。 + n = nums.len() + 時間計算量: O(n) + 空間計算量: O(1) + + テストケース nums=[0, 0, 1] out=[0, 1, 0]となり,wrong Answerとなった。 + ループ中に配列を変更するので、2つ目の0をループ中で飛ばしてしまうのが原因だと思った。 + for-loopではなく、while-loopにして0を見つけて移動させたときはインデックスをインクリメントしない方針で対応できそう。 + これまでに見た要素数を別でインクリメントしておいて、nums.len() - 1になったらループを抜けないと無限ループになる。 + + 何がわからなかったか + - そのままループで解けると思い込んでしまい、エッジケースでWrong Answerとなった。 + + 正解してから気づいたこと + - iter.remove(i)は時間計算量がO(n)なので、全体の時間計算量はO(n ^ 2)になる。 +*/ + +pub struct Solution {} +impl Solution { + pub fn move_zeroes(nums: &mut Vec) { + let mut i = 0; + let mut processed_nums_count = 0; + + while processed_nums_count < nums.len() { + processed_nums_count += 1; + + if nums[i] != 0 { + i += 1; + continue; + } + + let zero = nums.remove(i); + nums.push(zero); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step1_test() { + let mut before_nums = vec![0, 1, 0, 3, 12]; + let after_nums = vec![1, 3, 12, 0, 0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + + let mut before_nums = vec![0]; + let after_nums = vec![0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + + let mut before_nums = vec![0, 0, 1]; + let after_nums = vec![1, 0, 0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + } +} diff --git a/src/bin/step2.rs b/src/bin/step2.rs new file mode 100644 index 0000000..bd95b90 --- /dev/null +++ b/src/bin/step2.rs @@ -0,0 +1,95 @@ +// Step2 +// 目的: 自然な書き方を考えて整理する + +// 方法 +// Step1のコードを読みやすくしてみる +// 他の人のコードを2つは読んでみること +// 正解したら終わり + +// 以下をメモに残すこと +// 講師陣はどのようなコメントを残すだろうか? +// 他の人のコードを読んで考えたこと +// 改善する時に考えたこと + +/* + コメント集、他の人のコードを読んで考えたこと + https://discord.com/channels/1084280443945353267/1210494002277908491/1211368894669787226 + - Erase-remove idiom というものが関係あるらしい。 + https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom + - 実装例 + https://cplusplus.com/reference/algorithm/remove/ + C++のstd::removeでは指定された値を取り除くのにポインタによる操作を行っている。 + - 配列先頭のポインタfirst、配列末尾のポインタlast、削除対象の値valを引数に取っている。 + - result=firstとして同じアドレス位置を保持しておく。 + - firstがlastと等しくなるまでループを続ける。 + - firstポインタのアドレス位置に入っている値と、削除対象の値が等しくない時 + - resultとfirstのアドレスが異なるとき + - resultのアドレス位置にfirstのアドレス位置に入っている値を入れる。 + - resultのアドレス位置を次の位置にインクリメント + - firstのアドレス位置を次の位置にインクリメント + - resultは削除対象のvalを除いた配列末尾のアドレスを指すポインタとして返される + - ForwaredIteratorはConceptというC++の機能らしい。 + https://cpprefjp.github.io/reference/iterator/forward_iterator.html + このアルゴリズムだと0を一回のループで取り除いた後に、元の配列の長さになるまで0をpush()すれば時間計算量がO(N)になると理解した。 + + https://github.com/rihib/leetcode/pull/50#discussion_r1888189547 + > nums[zeroIndex], nums[i] = nums[i], nums[zeroIndex] + > これ C++ だと zeroIndex == i の場合は未定義動作にあたるかと思いますが、Go では大丈夫でしょうか。 + > この質問は、本当に不安に思っているというよりは、言語仕様を調べたことがありますか、それとも漫然と経験上書いていますか、という質問です。 + 自分はこのコードを読んで不安を感じることができなかった。配列から値を取り出して代入しているだけなので大丈夫なのではと感じた。 + + https://github.com/fhiyo/leetcode/pull/54#discussion_r1729230640 + > まとめて 0 fill は、loop unrolling できたりするのでちょっと嬉しいこともあるでしょう。 + loop unrollingを初めて聞いた。手作業による書き換えやコンパイラの最適化によって、同じ処理内容ではあるが処理回数を削減すること。 + CPUのレジスタをより多く使う必要がある、展開後のコードの方が長くなるのでコードサイズが増加するなどのトレードオフが発生する。 + https://ja.wikipedia.org/wiki/%E3%83%AB%E3%83%BC%E3%83%97%E5%B1%95%E9%96%8B + + https://github.com/Yoshiki-Iwasa/Arai60/pull/59/changes#diff-8201a9b64da970a353f0eb106023266ce67230b908a5213848bdfa1793d8574c + - Erase-remove idiomのRust実装例。retain(), resize()メソッドどちらも使ったことがなく新しいメソッドを知れた。 + + https://github.com/naoto-iwase/leetcode/pull/55/changes#diff-781326e99985e6db2eab57d073695b257877a3871c5b21a5e284c0dd82038c5dR128 + - 0と0ではない値を入れ替える(swap)実装。この実装を行っている人が多い印象だった。 + + 改善する時に考えたこと + - Vec::remove()ではなく、swapによる実装に変更して時間計算量をO(nums.len())に改善する + + 所感 + - 少しパズルに感じる。step2a.rsでErase-remove idiomを試してみる。 +*/ + +pub struct Solution {} +impl Solution { + pub fn move_zeroes(nums: &mut Vec) { + let mut swap_target_index = 0; + + for i in 0..nums.len() { + if nums[i] != 0 { + nums.swap(i, swap_target_index); + swap_target_index += 1; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2_test() { + let mut before_nums = vec![0, 1, 0, 3, 12]; + let after_nums = vec![1, 3, 12, 0, 0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + + let mut before_nums = vec![0]; + let after_nums = vec![0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + + let mut before_nums = vec![0, 0, 1]; + let after_nums = vec![1, 0, 0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + } +} diff --git a/src/bin/step2a.rs b/src/bin/step2a.rs new file mode 100644 index 0000000..ec9db61 --- /dev/null +++ b/src/bin/step2a.rs @@ -0,0 +1,59 @@ +// Step2a +// 目的: Erase-remove idiomの考え方で実装してみる。 + +// 方法 +// Step1のコードを読みやすくしてみる +// 他の人のコードを2つは読んでみること +// 正解したら終わり + +// 以下をメモに残すこと +// 講師陣はどのようなコメントを残すだろうか? +// 他の人のコードを読んで考えたこと +// 改善する時に考えたこと + +/* + https://github.com/Yoshiki-Iwasa/Arai60/pull/59/changes#diff-8201a9b64da970a353f0eb106023266ce67230b908a5213848bdfa1793d8574c + - Erase-remove idiomのRust実装例。retain(), resize()メソッドどちらも使ったことがなく新しいメソッドを知れた。 + + 所感 + - シンプルな考え方で一番わかりやすい。 + - 配列の順序を維持したまま0を取り除く。 + - 変更前の配列のサイズに等しくなるように配列末尾を0で埋める。 + - retainの実装(retain_mutを呼び出している)を少し読んだが、想定以上に長い実装だった。 + - 標準ライブラリで要求される効率性と安全性を担保しながら、配列をin-placeで読み書きするのは大変なんだなと思った。 + https://doc.rust-lang.org/nightly/src/alloc/vec/mod.rs.html#2432 +*/ + +pub struct Solution {} +impl Solution { + pub fn move_zeroes(nums: &mut Vec) { + let original_len = nums.len(); + // predicateに一致する値のみにする。0でない値を残す。 + nums.retain(|v| *v != 0); + // 元のサイズと同じになるように0を追加する。 + nums.resize(original_len, 0); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2a_test() { + let mut before_nums = vec![0, 1, 0, 3, 12]; + let after_nums = vec![1, 3, 12, 0, 0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + + let mut before_nums = vec![0]; + let after_nums = vec![0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + + let mut before_nums = vec![0, 0, 1]; + let after_nums = vec![1, 0, 0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + } +} diff --git a/src/bin/step3.rs b/src/bin/step3.rs new file mode 100644 index 0000000..33f70e7 --- /dev/null +++ b/src/bin/step3.rs @@ -0,0 +1,53 @@ +// Step3 +// 目的: 覚えられないのは、なんか素直じゃないはずなので、そこを探し、ゴールに到達する + +// 方法 +// 時間を測りながらもう一度解く +// 10分以内に一度もエラーを吐かず正解 +// これを3回連続でできたら終わり +// レビューを受ける +// 作れないデータ構造があった場合は別途自作すること + +/* + n = nums.len() + 時間計算量: O(n) + 空間計算量: O(1) +*/ + +/* + 1回目: 1分14秒 + 2回目: 0分35秒 + 3回目: 0分33秒 +*/ + +pub struct Solution {} +impl Solution { + pub fn move_zeroes(nums: &mut Vec) { + let original_len = nums.len(); + nums.retain(|v| *v != 0); + nums.resize(original_len, 0); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step3_test() { + let mut before_nums = vec![0, 1, 0, 3, 12]; + let after_nums = vec![1, 3, 12, 0, 0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + + let mut before_nums = vec![0]; + let after_nums = vec![0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + + let mut before_nums = vec![0, 0, 1]; + let after_nums = vec![1, 0, 0]; + Solution::move_zeroes(&mut before_nums); + assert_eq!(before_nums, after_nums); + } +}