From 134f3ebdaa1ac93e6ed5337e345f3cf64e305f31 Mon Sep 17 00:00:00 2001 From: t9a Date: Thu, 19 Mar 2026 09:12:04 +0900 Subject: [PATCH] solve: 6.Zigzag Conversion --- src/bin/step1.rs | 100 +++++++++++++++++++++++++++++++++++++++++++ src/bin/step2.rs | 105 ++++++++++++++++++++++++++++++++++++++++++++++ src/bin/step2a.rs | 94 +++++++++++++++++++++++++++++++++++++++++ src/bin/step3.rs | 92 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 391 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..23875e3 --- /dev/null +++ b/src/bin/step1.rs @@ -0,0 +1,100 @@ +// Step1 +// 目的: 方法を思いつく + +// 方法 +// 5分考えてわからなかったら答えをみる +// 答えを見て理解したと思ったら全部消して答えを隠して書く +// 5分筆が止まったらもう一回みて全部消す +// 正解したら終わり + +/* + 問題の理解 + - 文字列sと行数を表す整数num_rowsが与えられる。指定された行数に渡ってジグザグに並べ替えた文字列を返す。 + s="PAYPALISHIRING" num_rows=3 out="PAHNAPLSIIGYIR" + + 何を考えて解いていたか + - 紙に書いて規則性を見るのが良さそう。 + - 入出力例は以下のようになっている。 + - num_rows=3のとき、に0列目に3文字・1列目に1文字・2列目に3文字・3列目に1文字...となっている。 + - num_rows=4のとき、0列目に4文字・1列目に1文字・2列目に1文字・3列目に4文字・4列目に1文字・5列目に1文字・6列目に残りの文字...となっている。 + - i + num_rows - 1 列目の列でnum_rows文字出力している。この列以外は1文字だけ出力している + ここで手が止まったので答えを見る。 + + 何がわからなかったか + - 規則性のようなものを見つけたが、手作業でやる方法まではたどり着けなかった。 + + 解法の理解 + https://www.youtube.com/watch?v=Q2Tw6gcVEwc&t=1s + - NeetCodeの解説動画を見たがマジックナンバーだらけで分かりづらいと感じる。 + https://neetcode.io/solutions/zigzag-conversion + - このページに掲載されている「2. Iteration - II」の方が分かりやすそう + - LeetCode問題文でZigzagになった文字列の並びが示されているとおりに、0行目0列目からスタートしていくイメージだと理解した。 + - 0行目〜(num_rows - 1)行目まで進みながらs[i]の文字を配列にpushしていくと、i行目に対応する文字を配列に格納できる。 + - (num_rows - 1)に到達したら、逆順(num_rows - 1)行目~0行目に進みながら、i行目に対応する文字を配列に格納できる。 + - directionで行の進む方向を制御している。 + - 最終的に["abc","def","ghi"]のような配列が得られるので、concatで1つの文字列にまとめている。 + + 所感 + - 文字列の並びの規則性を見つけることにばかり気を取られて、手作業で問題文に示されている通りZigzagに文字をなぞっていく選択肢を思いつかなかった。 + 問題を解く時にいきなりきれいなアルゴリズムを考えるのではなくて、シンプルに手作業で解くということを実践したいなと思った。 + 感覚としてスマートな解法を考えることにばかり気を取られていて、問題に対して素直に考えられていないという感じがする。 +*/ + +pub struct Solution {} +impl Solution { + pub fn convert(s: String, num_rows: i32) -> String { + let num_rows: usize = match num_rows.try_into() { + Ok(v) => v, + Err(_) => panic!("num_rows must be positive value. num_rows: {}", num_rows), + }; + + if num_rows == 1 || s.len() <= num_rows { + return s; + } + + let mut rows = vec![String::new(); num_rows]; + let mut row = 0usize; + let mut direction = 1; + + for c in s.chars() { + rows[row].push(c); + row = ((row as i32) + direction) as usize; + if row == 0 || row == num_rows - 1 { + direction *= -1; + } + } + + rows.concat() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn step1_num_rows_negative_value_test() { + Solution::convert("PAYPALISHIRING".to_string(), -3); + } + + #[test] + fn step1_test() { + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 3), + "PAHNAPLSIIGYIR" + ); + + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 4), + "PINALSIGYAHRPI" + ); + + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 1), + "PAYPALISHIRING" + ); + + assert_eq!(Solution::convert("P".to_string(), 3), "P"); + } +} diff --git a/src/bin/step2.rs b/src/bin/step2.rs new file mode 100644 index 0000000..7b2e677 --- /dev/null +++ b/src/bin/step2.rs @@ -0,0 +1,105 @@ +// Step2 +// 目的: 自然な書き方を考えて整理する + +// 方法 +// Step1のコードを読みやすくしてみる +// 他の人のコードを2つは読んでみること +// 正解したら終わり + +// 以下をメモに残すこと +// 講師陣はどのようなコメントを残すだろうか? +// 他の人のコードを読んで考えたこと +// 改善する時に考えたこと + +/* + 他の人のコードを読んで考えたこと + https://github.com/olsen-blue/Arai60/pull/61/changes#diff-ccfa5b3e70552f7a1aea1f6a719cd00d74a1035c89cfab6426fddc4696ed3e21R56 + - 行を進める方向(direction)を1・-1で管理するのかboolによって管理するのかで結構別れている様子。boolとdirectionをenumで定義する方向で実装してみるのも良さそうだと思った。 + 書いていて思ったが、direction自体は関数の外側に露出しないのでenumは過剰な気がしてきた。boolによるフラグ管理で十分そう。 + + https://github.com/saagchicken/coding_practice/pull/22/changes#r2009508424 + > この問題、出題意図は、お手玉できるか、な気もします。 + - 同じようなことを思った。何をしようとしているかを理解して、これを素直にプログラムに落とし込めるかという感じ。 + + https://github.com/naoto-iwase/leetcode/pull/61#discussion_r2529679629 + > row_index など、行番号のニュアンスがあっても良いかなと思いました。row の場合、内容を読む前だと rows との対応関係がありそうにも見えます。 + - 確かにrowsに対してrowだと row = rows[i]のようにも見えるなと思った。 + + https://leetcode.com/problems/zigzag-conversion/solutions/333761/rust-0ms-4ms-by-obliquemotion-ceg6/ + LeetCode Solutionのトップにあった解法。読みづらいので練習としては良さそう。 + + 改善する時に考えたこと + - for-loopの中で row = ((row as i32) + direction) as usize; の部分がキャストでごちゃごちゃしているのをなんとかしたい。 + - rowをi32として扱えば少し良くなりそう。 + + 所感 + - step1.rsと比べて少し行数は増えたものの読み手の認知負荷が下がったように感じる。 +*/ + +pub struct Solution {} +impl Solution { + pub fn convert(s: String, num_rows: i32) -> String { + let num_rows: usize = match num_rows.try_into() { + Ok(v) => v, + Err(_) => panic!("num_rows must be positive value: num_rows: {}", num_rows), + }; + + if num_rows == 0 { + return "".to_string(); + } + if num_rows == 1 || s.chars().count() <= num_rows { + return s; + } + + let mut rows = vec![String::new(); num_rows]; + let mut i = 0; + let mut is_down_direction = true; + + for c in s.chars() { + rows[i as usize].push(c); + + if is_down_direction { + i += 1; + } else { + i -= 1; + } + + if i == 0 || i == num_rows - 1 { + is_down_direction = !is_down_direction; + } + } + + rows.concat() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn step2_num_rows_negative_value_test() { + Solution::convert("PAYPALISHIRING".to_string(), -3); + } + + #[test] + fn step2_test() { + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 3), + "PAHNAPLSIIGYIR" + ); + + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 4), + "PINALSIGYAHRPI" + ); + + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 1), + "PAYPALISHIRING" + ); + + assert_eq!(Solution::convert("P".to_string(), 3), "P"); + } +} diff --git a/src/bin/step2a.rs b/src/bin/step2a.rs new file mode 100644 index 0000000..13fe72e --- /dev/null +++ b/src/bin/step2a.rs @@ -0,0 +1,94 @@ +// Step2a +// 目的: 別の解法の写経と理解 + +/* + https://leetcode.com/problems/zigzag-conversion/solutions/333761/rust-0ms-4ms-by-obliquemotion-ceg6/ + LeetCode Solutionのトップにあった解法。読みづらいので練習としては良さそう。 + + 解法の理解 + 入力例: s="PAYPALISHIRING" num_rows=3 out="PAHNAPLSIIGYIR" + - (0..num_rows).chain((1..num_rows - 1).rev()) + - [0,1,2,1] + - cycleで[0,1,2,1]最後尾の要素まで到達すると先頭の要素に戻るイテレータを作っている + - zipでcycleのイテレータと文字のタプルのセットを生成している + zip(s.chars()); (row_index,c) -> (0,P) + zip(s.chars()); (row_index,c) -> (1,A) + zip(s.chars()); (row_index,c) -> (2,Y) + zip(s.chars()); (row_index,c) -> (1,P) + zip(s.chars()); (row_index,c) -> (0,A) + zip(s.chars()); (row_index,c) -> (1,L) + zip(s.chars()); (row_index,c) -> (2,I) + zip(s.chars()); (row_index,c) -> (1,S) + zip(s.chars()); (row_index,c) -> (0,H) + zip(s.chars()); (row_index,c) -> (1,I) + zip(s.chars()); (row_index,c) -> (2,R) + zip(s.chars()); (row_index,c) -> (1,I) + zip(s.chars()); (row_index,c) -> (0,N) + zip(s.chars()); (row_index,c) -> (1,G) + - zigzags.sort_by_keyの行位置のみでソートしている + zigzags.sort_by_key(); (row_index,c) -> (0,P) + zigzags.sort_by_key(); (row_index,c) -> (0,A) + zigzags.sort_by_key(); (row_index,c) -> (0,H) + zigzags.sort_by_key(); (row_index,c) -> (0,N) + zigzags.sort_by_key(); (row_index,c) -> (1,A) + zigzags.sort_by_key(); (row_index,c) -> (1,P) + zigzags.sort_by_key(); (row_index,c) -> (1,L) + zigzags.sort_by_key(); (row_index,c) -> (1,S) + zigzags.sort_by_key(); (row_index,c) -> (1,I) + zigzags.sort_by_key(); (row_index,c) -> (1,I) + zigzags.sort_by_key(); (row_index,c) -> (1,G) + zigzags.sort_by_key(); (row_index,c) -> (2,Y) + zigzags.sort_by_key(); (row_index,c) -> (2,I) + zigzags.sort_by_key(); (row_index,c) -> (2,R) + - ソートする時にタプルで指定すると、同じ行でアルファベットで並び替えされておかしな結果になるので、sort_by_keyによって明示的に行位置のみを対象にしてソートしている + + 所感 + - cycle(),zip()メソッドの動き方を理解するのに時間がかかった。 + 関数型の考え方?に慣れていないせいか、cycle()で生成したイテレータをzip()メソッドで文字列の各文字とタプルにしている部分が難しく感じた。inspect()を利用して何が行われているかを理解できた。 + https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.inspect + + - https://github.com/olsen-blue/Arai60/pull/61#discussion_r2040670667 + Pythonだが、近い発想で書かれているコードだなと思った。 +*/ + +pub struct Solution {} +impl Solution { + pub fn convert(s: String, num_rows: i32) -> String { + let mut zigzags = (0..num_rows) + .chain((1..num_rows - 1).rev()) + .cycle() + .zip(s.chars()) + // 複数のイテレータを連結しているコードのデバッグ手法の例として意図的にinspect()のコードを残しています + .inspect(|(row_index, c)| { + println!("zip(s.chars()); (row_index,c) -> ({},{})", row_index, c) + }) + .collect::>(); + zigzags.sort_by_key(|(row_index, _)| *row_index); + zigzags.into_iter().map(|(_, c)| c).collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2a_test() { + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 3), + "PAHNAPLSIIGYIR" + ); + + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 4), + "PINALSIGYAHRPI" + ); + + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 1), + "PAYPALISHIRING" + ); + + assert_eq!(Solution::convert("P".to_string(), 3), "P"); + } +} diff --git a/src/bin/step3.rs b/src/bin/step3.rs new file mode 100644 index 0000000..1ab9e76 --- /dev/null +++ b/src/bin/step3.rs @@ -0,0 +1,92 @@ +// Step3 +// 目的: 覚えられないのは、なんか素直じゃないはずなので、そこを探し、ゴールに到達する + +// 方法 +// 時間を測りながらもう一度解く +// 10分以内に一度もエラーを吐かず正解 +// これを3回連続でできたら終わり +// レビューを受ける +// 作れないデータ構造があった場合は別途自作すること + +/* + n = s.chars().count() + 時間計算量: O(n) + 空間計算量: O(n) +*/ + +/* + 1回目: 5分18秒 + 2回目: 3分15秒 + 3回目: 2分54秒 +*/ + +/* + 所感 + - 行の移動方向制御はフラグ管理の実装にした。少し冗長になるものの、iが単調増加・減少することが一目で分かるので読みやすいという感覚。 +*/ + +pub struct Solution {} +impl Solution { + pub fn convert(s: String, num_rows: i32) -> String { + let num_rows: usize = num_rows + .try_into() + .expect("num_rows must be positive value"); + + if num_rows == 0 { + return "".to_string(); + } + if num_rows == 1 || s.chars().count() <= num_rows { + return s; + } + + let mut rows = vec![String::new(); num_rows]; + let mut is_down_direction = true; + let mut i = 0; + for c in s.chars() { + rows[i].push(c); + + if is_down_direction { + i += 1; + } else { + i -= 1; + } + + if i == 0 || i == num_rows - 1 { + is_down_direction = !is_down_direction; + } + } + + rows.concat() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn step3_num_rows_negative_value_test() { + Solution::convert("PAYPALISHIRING".to_string(), -3); + } + + #[test] + fn step3_test() { + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 3), + "PAHNAPLSIIGYIR" + ); + + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 4), + "PINALSIGYAHRPI" + ); + + assert_eq!( + Solution::convert("PAYPALISHIRING".to_string(), 1), + "PAYPALISHIRING" + ); + + assert_eq!(Solution::convert("P".to_string(), 3), "P"); + } +}