-
Notifications
You must be signed in to change notification settings - Fork 0
22. Generate Parentheses #6
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
|
|
||
| ## step1: | ||
| nのParenthesesをn-1のParenthesesから生成するとなったとき、n-1での各文字列に対して左から右へ()を挿入していく場所を探していくと、 | ||
| "("に当たった時はその"("を"()"で囲むか、"()("のように左に添える2パターンがあり、")"に当たった時は"())"のように置くパターンがあり、""に当たった時はそこに"()"をおけば良い。それを素直にコードにすると以下のようになる。 | ||
| ### code | ||
| ```python | ||
| class Solution: | ||
| # when facing (, put () or surround the ( with () | ||
| # when facing ), put () before ) | ||
| # when facing "", put () | ||
| def generateParenthesis(self, n: int) -> List[str]: | ||
| answer = [] | ||
| def backtrack(parenthesis, m): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 一般論として、関数名はアルゴリズム名よりは、実態に即した別のもののほうが良いかなと思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. なるほど、以前もdfsと名付けたのをtraverseと命名する感じのことを指摘されましたがこの場合でも気をつけるようにします。 |
||
| if m == 0: | ||
| return parenthesis | ||
| next_parenthesis = set() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. この問題では順番は求められていませんが、一般にsetを使った結果をそのままlist()して返却すると、順番はぐちゃぐちゃになります。 |
||
| for parenthe in parenthesis: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. parenthesisが単数形であることは後々知りました。最初はparenthesesを単数形にするとなんだろうとなって結局parentheとぶつ切りにする感じにしてしまいました... |
||
| for i in range(len(parenthe)+1): | ||
| if i < len(parenthe) and parenthe[i] == "(": | ||
| next_parenthesis.add(parenthe[:i] + "()" + parenthe[i:]) | ||
| index_to_insert_closing = i + find_index_of_corresponding_symbol(parenthe[i:]) | ||
| next_parenthesis.add(parenthe[:i] + "(" + parenthe[i:index_to_insert_closing] + ")" + parenthe[index_to_insert_closing:]) | ||
| else: | ||
| next_parenthesis.add(parenthe[:i] + "()" + parenthe[i:]) | ||
| return backtrack(next_parenthesis, m - 1) | ||
|
|
||
| def find_index_of_corresponding_symbol(part): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. この関数名で突然symbolが出てきているので、parenthesisで良いかなと思います。ただ、関数名が長いので、find_index_of_closerぐらいが良いかなと思いました。 が、よくよく考えると、partを受け取って最初の(が閉じる位置を返却する、なので、(未定義ですが)実態としてはfind_first_edge(_index)とかfind_first_break(_index)とかですかね。 |
||
| opening = 0 | ||
| closing = 0 | ||
| for i in range(len(part)): | ||
| if part[i] == "(": | ||
| opening += 1 | ||
| else: | ||
| closing += 1 | ||
| if opening == closing: | ||
| return i | ||
| return -1 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. この関数単体で見たときに-1はよくあるfindの動作だと思いますが、このプログラムが正しく動作している限りにおいてはここには来ないので、そのコメントがあると親切かなと思いました。 |
||
|
|
||
| return list(backtrack([""], n)) | ||
| ``` | ||
| これでacceptされたが、geminiに読ませてみると以下の問題を言われた。 | ||
| ## 非効率な全探索(Setによる管理) | ||
| 今のコードは、各ステップで「既存の文字列の全箇所に () を入れる」という操作を繰り返しています。これは計算量が非常に多くなり、n が大きくなると set への追加と文字列操作でパフォーマンスが劇的に悪化します。 | ||
|
|
||
| ## 「バックトラック」の一般的な解法との違い | ||
| 通常、この問題(LeetCode 22. Generate Parentheses)をバックトラックで解く場合、**「空の状態から1文字ずつ ( か ) を足していく」**という手法をとります。 | ||
|
|
||
| なのでこれをヒントに一回1から書いてみる。まず作りたいparenthesesは2*nの長さあり、それぞれに対してnこの(とnこの)をそれぞれ挿入していく。ここでwell-formedにするにはすでに(を配置した数分より大きい)を途中で置いてはいけない。この(と)の配置を全探索していくと答えにつながる。backtrackでも解けるがちょっと捻ってstackを使ったdfsで解いてみる。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def generateParenthesis(self, n: int) -> List[str]: | ||
| answer = [] | ||
| parentheses_and_open_and_close = [] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これは「(parentheses: str, open: int, close: int)のtupleと同じ中身のlist」のlistだと思いますが、名前がかなりわかりにくいです。 parentheses_and_open_and_close: list[tuple[str, int, int]] = []とかでよいと思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AIの提案通りにstackやstatesにすると個人的には何が入ってるか迷ってしまいそうなので |
||
| parentheses_and_open_and_close.append(["", n, n]) | ||
| while parentheses_and_open_and_close: | ||
| parentheses, open_num, close_num = parentheses_and_open_and_close.pop() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. open_num, close_numはremaining_open, remaining_closeみたいな、残りのopen数、残りのclose数、みたいなことがわかる変数名のほうが適切かなと思いました。 |
||
| if open_num == close_num == 0: | ||
| answer.append(parentheses) | ||
| continue | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. このcontinueは後ろのifの評価を避ける程度の意味しかないので、なくてよいかなと思いました。好みの問題かもしれません。 |
||
| if open_num > 0: | ||
| parentheses_and_open_and_close.append([parentheses + "(", open_num - 1, close_num]) | ||
| if close_num > 0 and close_num > open_num: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. close_num > 0は、open_numが負にならないので不要ですかね。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
これはそうですね。
これも正しいのですが、open_num, close_numを残りの"(", ")"を置ける数と定義している以上最後はopen_num == close_num == 0と置いた方が読み手に終了条件が伝わりやすくていいかなと個人的に思いました。 |
||
| parentheses_and_open_and_close.append([parentheses + ")", open_num, close_num - 1]) | ||
| return answer | ||
| ``` | ||
| これだと(と)に対してそれぞれの文字位置に対して置く、置かないを羅派しているのでset()で重複を省く必要がない。 | ||
|
|
||
| ## step2: | ||
| ### code | ||
| ```python | ||
| class Solution: | ||
| def generateParenthesis(self, n: int) -> List[str]: | ||
| answer = [] | ||
| parentheses_and_open_and_close = [] | ||
| parentheses_and_open_and_close.append(["", n, n]) | ||
| while parentheses_and_open_and_close: | ||
| parentheses, open_num, close_num = parentheses_and_open_and_close.pop() | ||
| if open_num == close_num == 0: | ||
| answer.append(parentheses) | ||
| continue | ||
| if open_num > 0: | ||
| parentheses_and_open_and_close.append([parentheses + "(", open_num - 1, close_num]) | ||
| if close_num > 0 and close_num > open_num: | ||
| parentheses_and_open_and_close.append([parentheses + ")", open_num, close_num - 1]) | ||
| return answer | ||
| ``` | ||
|
|
||
| ## step3: | ||
| どうせなのでbacktrack, bfsで解いてみる。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. step3は、最終的に自分が妥当だと思うコードをすぐに書けるようにする定着の目的もあります。意図的に自分が最も妥当だと思うコードを選んで練習するのがよいと思います。 |
||
| ### code | ||
| ```python | ||
| class Solution: | ||
| def generateParenthesis(self, n: int) -> List[str]: | ||
| answer = [] | ||
|
|
||
| def backtrack(parentheses, open_num, close_num): | ||
| if open_num == close_num == 0: | ||
| answer.append(parentheses) | ||
| if open_num > 0: | ||
| backtrack(parentheses + "(", open_num - 1, close_num) | ||
| if close_num > 0 and close_num > open_num: | ||
| backtrack(parentheses + ")", open_num, close_num - 1) | ||
|
|
||
| backtrack("", n, n) | ||
| return answer | ||
| ``` | ||
|
|
||
| ```python | ||
| from collections import deque | ||
| class Solution: | ||
| def generateParenthesis(self, n: int) -> List[str]: | ||
| answer = [] | ||
| parentheses_open_close = deque() | ||
| parentheses_open_close.append(["", n, n]) | ||
|
|
||
| while parentheses_open_close: | ||
| parentheses, open_num, close_num = parentheses_open_close.popleft() | ||
| if open_num == close_num == 0: | ||
| answer.append(parentheses) | ||
| continue | ||
| if open_num > 0: | ||
| parentheses_open_close.append([parentheses + "(", open_num - 1, close_num]) | ||
| if close_num > 0 and open_num < close_num: | ||
| parentheses_open_close.append([parentheses + ")", open_num, close_num - 1]) | ||
|
|
||
| return answer | ||
| ``` | ||

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.
この変数は名前を改善したほうがよいと思ったのですが、そもそもどこでも使っていないですか?
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.
この変数使ってないですね。削除しておくべきですがもし使うとしたらそのままparenthesisと名付けるのが自然ですかね