From a8a5d3c8e966c1aafbd2d1cba8507086883f3be2 Mon Sep 17 00:00:00 2001 From: t9a Date: Mon, 23 Feb 2026 14:18:02 +0900 Subject: [PATCH] solve: 22.Generate Parentheses --- src/bin/step1.rs | 106 +++++++++++++++++++++++++++++++++ src/bin/step1a.rs | 73 +++++++++++++++++++++++ src/bin/step2.rs | 145 ++++++++++++++++++++++++++++++++++++++++++++++ src/bin/step2a.rs | 63 ++++++++++++++++++++ src/bin/step3.rs | 101 ++++++++++++++++++++++++++++++++ 5 files changed, 488 insertions(+) create mode 100644 src/bin/step1.rs create mode 100644 src/bin/step1a.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..6b8b0f0 --- /dev/null +++ b/src/bin/step1.rs @@ -0,0 +1,106 @@ +// Step1 +// 目的: 方法を思いつく + +// 方法 +// 5分考えてわからなかったら答えをみる +// 答えを見て理解したと思ったら全部消して答えを隠して書く +// 5分筆が止まったらもう一回みて全部消す +// 正解したら終わり + +/* + 問題の理解 + - 自然数nが与えられる。n個の括弧の全ての組み合わせを返す。 + n=1のとき["()"] 01(1) + n=2のとき["(())", "()()"] 0011(3) 0101(5) + n=3のとき["((()))", "(()())", "(())()", "()(())", "()()()"] 000111(7) 001011(11) 010011(19) 010101(21) + + 何を考えて解いていたか + - 何も思いつかないので、手作業でやることを考える。 + - '('のとき次に来て良いのは'(', ')' + - ')'のとき次に来て良いのは')', '(' + - n個の'(', ')'の並びから後続の'(', ')'の並びが一意になるかと思ったがそうでもない + - '(' = 0, ')' = 1としたときのビット列の並びを眺めてみてもとくに何も思いつかない。 + ナイーブな実装も思いつかず手が止まったので実装例を見て理解する。 + + 何がわからなかったか + - 有効なペアを維持しながら、全パターンを出力するアルゴリズムが分からなかった。 + + 実装例の理解 + https://leetcode.com/problems/generate-parentheses/solutions/5976224/complex-backtracking-interview-prepare-l-keak/ + - 組み合わせの文字列に出現する'(', ')'の数は必ずnと等しくなることに注目している。 + - '('が最初にn個連続しても不正なペアは生成されない + - if open_count < n + - '(((' -> ')))' = "((()))" + - ')'は先に出現している'('の個数を超えて連続すると不正なペアが生成される + - '(' -> '))' -> '(' = "())(" 不正なペアが生成される + つまり、先に出現している'('の個数を超えない範囲で')'を追加すると不正なペアは生成されない + - if close_count < open_count + - '(' -> ')' -> '(' -> ')' = "()()" + + 所感 + - 実装例を読めば理解できるものの、自分ではナイーブな実装も思いつかなかったので繰り返し練習が必要だと感じる。 + - とりあえず、スタックの実装に書き直す練習をしておく。(step1a.rs) +*/ + +pub struct Solution {} +impl Solution { + pub fn generate_parenthesis(n: i32) -> Vec { + let mut all_parenthesis = Vec::new(); + let mut parenthesis = String::new(); + + Self::make_parenthesis(n, 0, 0, &mut parenthesis, &mut all_parenthesis); + + all_parenthesis + } + + fn make_parenthesis( + n: i32, + open_count: i32, + close_count: i32, + parenthesis: &mut String, + all_parenthesis: &mut Vec, + ) { + if open_count == n && close_count == n { + all_parenthesis.push(parenthesis.clone()); + return; + } + + if open_count < n { + parenthesis.push('('); + Self::make_parenthesis(n, open_count + 1, close_count, parenthesis, all_parenthesis); + parenthesis.pop(); + } + + if close_count < open_count { + parenthesis.push(')'); + Self::make_parenthesis(n, open_count, close_count + 1, parenthesis, all_parenthesis); + parenthesis.pop(); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step1_test() { + let mut expect = vec!["()"]; + let mut actual = Solution::generate_parenthesis(1); + expect.sort(); + actual.sort(); + assert_eq!(expect, actual); + + let mut expect = vec!["(())", "()()"]; + let mut actual = Solution::generate_parenthesis(2); + expect.sort(); + actual.sort(); + assert_eq!(expect, actual); + + let mut expect = vec!["((()))", "(()())", "(())()", "()(())", "()()()"]; + let mut actual = Solution::generate_parenthesis(3); + expect.sort(); + actual.sort(); + assert_eq!(actual, expect); + } +} diff --git a/src/bin/step1a.rs b/src/bin/step1a.rs new file mode 100644 index 0000000..a78642e --- /dev/null +++ b/src/bin/step1a.rs @@ -0,0 +1,73 @@ +// Step1a +// 目的: 再帰からスタックを利用した実装に書き換える練習 + +/* + 所感 + - parenthesisをfrontierにpush()するときに毎回clone()しているのが気になったがこの実装では仕方がないと思った。 + 再帰処理ではparenthesis.pop()されるのが再帰処理が実行された後だが、この実装ではfrontierにpushした後にすぐparenthesis.pop()が実行されるので厳密に処理の順序が等価ではないことが原因だと思った。 +*/ + +pub struct Solution {} +impl Solution { + pub fn generate_parenthesis(n: i32) -> Vec { + let mut all_parenthesis = Vec::new(); + let mut frontier = Vec::new(); + + frontier.push((0, 0, String::new())); + while let Some((open_bracket_count, close_bracket_count, mut parenthesis)) = frontier.pop() + { + if open_bracket_count == n && close_bracket_count == n { + all_parenthesis.push(parenthesis); + continue; + } + + if open_bracket_count < n { + parenthesis.push('('); + frontier.push(( + open_bracket_count + 1, + close_bracket_count, + parenthesis.clone(), + )); + parenthesis.pop(); + } + + if close_bracket_count < open_bracket_count { + parenthesis.push(')'); + frontier.push(( + open_bracket_count, + close_bracket_count + 1, + parenthesis.clone(), + )); + parenthesis.pop(); + } + } + + all_parenthesis + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step1a_test() { + let mut expect = vec!["()"]; + let mut actual = Solution::generate_parenthesis(1); + expect.sort(); + actual.sort(); + assert_eq!(expect, actual); + + let mut expect = vec!["(())", "()()"]; + let mut actual = Solution::generate_parenthesis(2); + expect.sort(); + actual.sort(); + assert_eq!(expect, actual); + + let mut expect = vec!["((()))", "(()())", "(())()", "()(())", "()()()"]; + let mut actual = Solution::generate_parenthesis(3); + expect.sort(); + actual.sort(); + assert_eq!(actual, expect); + } +} diff --git a/src/bin/step2.rs b/src/bin/step2.rs new file mode 100644 index 0000000..90e25a4 --- /dev/null +++ b/src/bin/step2.rs @@ -0,0 +1,145 @@ +// Step2 +// 目的: 自然な書き方を考えて整理する + +// 方法 +// Step1のコードを読みやすくしてみる +// 他の人のコードを2つは読んでみること +// 正解したら終わり + +// 以下をメモに残すこと +// 講師陣はどのようなコメントを残すだろうか? +// 他の人のコードを読んで考えたこと +// 改善する時に考えたこと + +/* + コメント集、他の人のコードを読んで考えたこと + https://github.com/hroc135/leetcode/pull/50/changes#r2052246310 + - strings.Builderについて。C#で使ったことあるなと思った。 + C#ではString型はimmutableなので、文字列結合を行うと新たに結合後の文字列を保持するメモリ領域を毎回確保することになる。 + StringBuilderではmutableなので、一度確保したメモリ領域を伸長しながら追加を行えるので、毎回メモリ確保が走らなくなり、比較的高速に処理できると理解した。 + Go言語でもStringはimmutableなので、文字列結合を頻繁に行うのであればStrings.Builderを利用することで比較的高速になると理解した。 + https://stackoverflow.com/questions/36720693/immutable-strings-in-go + Rustではデフォルトで変数の値はimmutableであり、mut キーワードによってmutableにすることが可能。 + 上記のStack Overflowに貼られている以下のリンクの説明で、Go言語のStringはimmutableなので複数の文字列が同じストレージを共有しても安全ですとある。 + > A string is represented in memory as a 2-word structure containing a pointer to the string data and a length. Because the string is immutable, it is safe for multiple strings to share the same storage, + https://research.swtch.com/godata + 不変性によりマルチスレッドプログラミングなどの場面で安全性の確保を行っていると理解した。 + Rust | Go | 可変性 + ------- ----------------- ----------- + String | strings.Builder | mutable + &str | string | immutable + マルチスレッドの文脈も関係あるかなと思ってRustドキュメントの並行性の箇所を眺めていたところGo言語の設計思想について言及されていた。 + https://doc.rust-jp.rs/book-ja/ch16-02-message-passing.html + Go言語における並列プログラミングではメモリを直接共有するのではなく、通信(チャネル)を通じてメモリを共有するという設計思想であることが分かった。 + > Do not communicate by sharing memory; instead, share memory by communicating. + https://go.dev/doc/effective_go#concurrency + Rustではチャネル、ロックによる並行性を扱う方法が提供されていることが分かった。ロックによる状態共有しか頭になかったので、色々なモデルがあって面白いなと思った。 + 書籍「プログラミングRust 第2版」にも並列性の章でチャネルについて言及されている。面白そうだと思うものの自分の経験してきた範囲では使い所があまり分からないという感じでもある。 + Rustでは所有権システムにより、String(mutable)を複数スレッドで共有できない(コンパイラが弾く)が、Go言語ではstrings.Builderをchannelで共有できてしまう点が面白いと思った。 + Go言語のstrings.Builderでは空でないstrings.Builderをコピーするなとコメントで警告している。 + copyCheck関数で実行時にアドレスの比較をし、コピーを検出してpanicさせている。無秩序にスレッド間でメモリを共有してレースコンディションなどの競合状態を発生させるくらいなら実行時エラーにするという意思を感じる。 + > // Do not copy a non-zero Builder. + https://go.dev/src/strings/builder.go + 実行時エラーになるコードが書けた。 + https://go.dev/play/p/7bABd2P_uZ3 + Rustはコンパイル時に検出できるからGo言語より優れているという主張をしたわけではなくて、言語仕様や設計哲学による言語間の違いを確認できて面白かった。 + + https://github.com/olsen-blue/Arai60/pull/54#discussion_r2020125121 + - 問題の入力と出力が分かっている状態で、出力をどのように網羅的に分類するかという考え方。 + 入力に対応する出力のパターンを網羅できるパターンを見つけることが第一段階で、見つけたパターンをコードにできるかということだと理解した。 + step1.rsでは問題からパターンを見つけることができず、実装例からどのようなパターンに分類しているかを理解していた。 + 繰り返し練習することで、様々なパターンを分類する能力を鍛えていくんだななどと考えた。 + + https://github.com/olsen-blue/Arai60/pull/54#discussion_r2022014586 + - ここのやり取りで言われている「(A)B」と分ける考え方がよく分からないので例示されているコードを写経してみる。(step2a.rs) + > https://github.com/olsen-blue/Arai60/pull/54#discussion_r2027288220 + + https://github.com/olsen-blue/Arai60/pull/54#discussion_r2022554077 + - 0個以上の変数名は複数形にするとより良いのではないかというコメント。同意できるので取り入れる。 + + https://github.com/skypenguins/coding-practice/pull/27#discussion_r2533462366 + - 計算量の話(カタラン数)とPythonにおいて文字列はimmutableなので素朴に結合すると計算量が悪化しそうだが、文字列結合はネイティブコードで実行されるのでPythonインタープリター比で十分に速く、気にするほどではないという話。 + + - base_caseの条件を if open_brackets_count + close_brackets_count == n * 2 としているコードも見かけたが、2というマジックナンバーを避けたいと思った。 + + 改善する時に考えたこと + - open_bracket_count -> open_brackets_countのように複数形にする + + 所感 + - backtrackingアルゴリズムとは直接関係のないstrings.Builderについて調べ始めたらかなり横道にそれてしまった感があるが、コーディング練習を通じて知らなかったことを知れたので良しとする。 +*/ + +pub struct Solution {} +impl Solution { + pub fn generate_parenthesis(n: i32) -> Vec { + let mut all_parenthesis = Vec::new(); + let mut parenthesis = String::new(); + + Self::make_parenthesis(n, 0, 0, &mut parenthesis, &mut all_parenthesis); + + all_parenthesis + } + + fn make_parenthesis( + n: i32, + open_brackets_count: i32, + close_brackets_count: i32, + parenthesis: &mut String, + all_parenthesis: &mut Vec, + ) { + if open_brackets_count == n && close_brackets_count == n { + all_parenthesis.push(parenthesis.clone()); + return; + } + + if open_brackets_count < n { + parenthesis.push('('); + Self::make_parenthesis( + n, + open_brackets_count + 1, + close_brackets_count, + parenthesis, + all_parenthesis, + ); + parenthesis.pop(); + } + + if close_brackets_count < open_brackets_count { + parenthesis.push(')'); + Self::make_parenthesis( + n, + open_brackets_count, + close_brackets_count + 1, + parenthesis, + all_parenthesis, + ); + parenthesis.pop(); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2_test() { + let mut expect = vec!["()"]; + let mut actual = Solution::generate_parenthesis(1); + expect.sort(); + actual.sort(); + assert_eq!(expect, actual); + + let mut expect = vec!["(())", "()()"]; + let mut actual = Solution::generate_parenthesis(2); + expect.sort(); + actual.sort(); + assert_eq!(expect, actual); + + let mut expect = vec!["((()))", "(()())", "(())()", "()(())", "()()()"]; + let mut actual = Solution::generate_parenthesis(3); + expect.sort(); + actual.sort(); + assert_eq!(actual, expect); + } +} diff --git a/src/bin/step2a.rs b/src/bin/step2a.rs new file mode 100644 index 0000000..0ccbb23 --- /dev/null +++ b/src/bin/step2a.rs @@ -0,0 +1,63 @@ +// Step2a +// 目的: 他の人のコードを写経する + +/* + https://github.com/olsen-blue/Arai60/pull/54#discussion_r2022014586 + - ここのやり取りで言われている「(A)B」と分ける考え方がよく分からないので例示されているコードを写経する。 + > https://github.com/olsen-blue/Arai60/pull/54#discussion_r2027288220 + + 解法の理解 + - 0 < n のとき"()"は解の中に必ず存在する。 + - n == 1 をbase_caseとして"()"を返している部分が a に対応しているように見える。 + - n - 1 - i としている箇所は n - 1個の"()"が b に対応しているように見える。 + + 所感 + - この解法にたどり着くまでの道のりが理解出来ない。突飛すぎると感じる。 +*/ + +pub struct Solution {} +impl Solution { + pub fn generate_parenthesis(n: i32) -> Vec { + if n == 0 { + return vec!["".to_string()]; + } + + let mut all_parenthesis = Vec::new(); + + for i in 0..n { + for a in Self::generate_parenthesis(i) { + for b in Self::generate_parenthesis(n - 1 - i) { + all_parenthesis.push(format!("({}){}", a, b)); + } + } + } + + all_parenthesis + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2a_test() { + let mut expect = vec!["()"]; + let mut actual = Solution::generate_parenthesis(1); + expect.sort(); + actual.sort(); + assert_eq!(expect, actual); + + let mut expect = vec!["(())", "()()"]; + let mut actual = Solution::generate_parenthesis(2); + expect.sort(); + actual.sort(); + assert_eq!(expect, actual); + + let mut expect = vec!["((()))", "(()())", "(())()", "()(())", "()()()"]; + let mut actual = Solution::generate_parenthesis(3); + expect.sort(); + actual.sort(); + assert_eq!(actual, expect); + } +} diff --git a/src/bin/step3.rs b/src/bin/step3.rs new file mode 100644 index 0000000..019d2b0 --- /dev/null +++ b/src/bin/step3.rs @@ -0,0 +1,101 @@ +// Step3 +// 目的: 覚えられないのは、なんか素直じゃないはずなので、そこを探し、ゴールに到達する + +// 方法 +// 時間を測りながらもう一度解く +// 10分以内に一度もエラーを吐かず正解 +// これを3回連続でできたら終わり +// レビューを受ける +// 作れないデータ構造があった場合は別途自作すること + +/* + N = n + 時間計算量: O(4 ^ N / sqrt(N)) + 空間計算量: O(4 ^ N / sqrt(N)) + 他の人のコードを読んでいる時に計算量がカタラン数と呼ばれるものになると知ったが、自分で見積もることはできなかった。 + ソフトウェアエンジニアの常識には含まれていないとのこと。 + - https://github.com/Ryotaro25/leetcode_first60/pull/58#discussion_r1997665903 + + - 電気通信大学「離散数理工学」のスライド + http://dopal.cs.uec.ac.jp/okamotoy/lect/2024/dme/dme2024lect05.pdf +*/ + +/* + 1回目: 3分47秒 + 繰り返し書いても得るものが無さそうなので2,3回目はスキップ +*/ + +pub struct Solution {} +impl Solution { + pub fn generate_parenthesis(n: i32) -> Vec { + let mut all_parenthesis = Vec::new(); + let mut parenthesis = String::new(); + + Self::make_parenthesis(n, 0, 0, &mut parenthesis, &mut all_parenthesis); + + all_parenthesis + } + + fn make_parenthesis( + n: i32, + open_brackets_count: i32, + close_brackets_count: i32, + parenthesis: &mut String, + all_parenthesis: &mut Vec, + ) { + if open_brackets_count == n && close_brackets_count == n { + all_parenthesis.push(parenthesis.clone()); + return; + } + + if open_brackets_count < n { + parenthesis.push('('); + Self::make_parenthesis( + n, + open_brackets_count + 1, + close_brackets_count, + parenthesis, + all_parenthesis, + ); + parenthesis.pop(); + } + + if close_brackets_count < open_brackets_count { + parenthesis.push(')'); + Self::make_parenthesis( + n, + open_brackets_count, + close_brackets_count + 1, + parenthesis, + all_parenthesis, + ); + parenthesis.pop(); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step3_test() { + let mut expect = vec!["()"]; + let mut actual = Solution::generate_parenthesis(1); + expect.sort(); + actual.sort(); + assert_eq!(expect, actual); + + let mut expect = vec!["(())", "()()"]; + let mut actual = Solution::generate_parenthesis(2); + expect.sort(); + actual.sort(); + assert_eq!(expect, actual); + + let mut expect = vec!["((()))", "(()())", "(())()", "()(())", "()()()"]; + let mut actual = Solution::generate_parenthesis(3); + expect.sort(); + actual.sort(); + assert_eq!(actual, expect); + } +}