-
Notifications
You must be signed in to change notification settings - Fork 0
solve: 22.Generate Parentheses #53
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
Open
t9a-dev
wants to merge
1
commit into
main
Choose a base branch
from
22.Generate-Parentheses
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<String> { | ||
| 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<String>, | ||
| ) { | ||
| 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); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<String> { | ||
| 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); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<String> { | ||
| 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<String>, | ||
| ) { | ||
| 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); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<String> { | ||
| 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); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
まずですね、列挙する話なので、列挙対象の全体を思い浮かべます。
でかいのでぼやけていてもいいので思い浮かべます。
列挙というのは、これをダブりヌケモレなく並べることですね。
これを手分けして並べることを考えましょう。(ここで手分けという概念を使っているのは、人間同士は他の人がやったことが見えないからですね。)なので、分類したくなります。
担当の分け方は色々あるんです。どんな分け方でも構いません。
ただ、一つの分け方として、はじめの一文字目は必ず"("だから、これに対応する")"の位置で分類することができますね。で、担当0から担当n-1までを用意して手分けします。
それぞれの担当のその先? そこは再帰するんですよ。