Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions 22. Generate Parentheses/22. Generate Parentheses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# 22. Generate Parentheses
## STEP1
- 何も見ずに解いてみる
- 単に再帰で () を至るところに挿入することを考えたが、重複して生成するのを防ぐ必要があり、set で管理すればできる。
```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
if n <= 0:
raise ValueError("n must be positive.")
if n == 1:
return ["()"]

results = set()
for s in self.generateParenthesis(n - 1):
for i in range(len(s) + 1):
results.add(s[:i] + "()" + s[i:])
return list(results)
```

- そもそも重複せずに発生する方法も書けるか? "(" の数と ")" の数を管理して書く。
```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
if n <= 0:
raise ValueError("n must be positive.")

results = []

def generate_helper(parentheses: str, num_open: int, num_close: int) -> None:
if num_open == 0 and num_close == 0:
results.append(parentheses)
return

if num_open == num_close:
generate_helper(parentheses + "(", num_open - 1, num_close)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generate_helper(parentheses + "(", num_open - 1, num_close)

が 2 回登場するのが気になりました。 step3 で重複をなくせているのはとても良いと思います。

return

if num_open > 0:
generate_helper(parentheses + "(", num_open - 1, num_close)
generate_helper(parentheses + ")", num_open, num_close - 1)

generate_helper("", n, n)
return results
```
- 上のコードは毎回新しい文字列を構築している。list を使って書き直す。
- ついでに "(" の追加をまとめて書く。"(" が使える限りは必ず追加できる。"(" と ")" が同数の場合は ")" は追加できないので終了。
```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
if n <= 0:
raise ValueError("n must be positive.")

results = []

def generate_helper(parentheses: list[str], num_open: int, num_close: int) -> None:
if num_open == 0 and num_close == 0:
results.append("".join(parentheses))
return

if num_open > 0:
parentheses.append("(")
generate_helper(parentheses, num_open - 1, num_close)
parentheses.pop()
if num_open == num_close:
return
parentheses.append(")")
generate_helper(parentheses, num_open, num_close - 1)
parentheses.pop()

generate_helper([""], n, n)
return results
```
- 手元で実行時間を計測したところ (10000 回の平均)、n = 8 で、文字列に追記する場合が 0.43 ms, list に追加する場合が 0.48 ms で、文字列に追記しても遅いわけではなかった。理由として、短い文字列であること、CPythonの場合は最適化される場合があること、が考えられるところでしょうか。
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

実測して検証しているのはよいと思います。
面接だと、「一般には O(N) になる可能性があるが、xx の実装では yy の条件で最適化されることが保証されるのでこうしました。保証されない可能性があれば zz のように書くと思います」くらいがさらっと言えたら十分じゃないでしょうか (どういう条件でこの最適化が起きるかの議論があったように思いますが、ちょっと正確に覚えていません)。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そうですね。どの条件で最適化が起こるかは把握までできていません。

https://discord.com/channels/1084280443945353267/1200089668901937312/1210619083385479258

つまり、気にしているのは、オーダーが正しく動くか、ではなくて、半年後に読んだ別の同僚が不安にならないか、環境の変化に対して頑健か、なのです。

今後は list を使って join する書き方をすると思います。


## STEP2
### プルリクやドキュメントを参照
- https://github.com/olsen-blue/Arai60/pull/54/files
- generate_helper([""], n, n) として括弧が両方 0 になれば終了か、generate_helper([""], 0, 0) として括弧が両方 n になれば終了かでは、前者の方が関数の処理が n に依存していないのでよいのではと思った。
- https://github.com/shining-ai/leetcode/blob/main/arai60/50-53_Greedy_Backtracking/53_22_Generate%20Parentheses/rework.py
- num_open > 0 で "(" 追加、num_open < num_close: で ")" 追加という書き方もわかりやすいですね。
## STEP3
### 3回ミスなく書く
```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
if n <= 0:
raise ValueError("n must be positive.")

results = []

def generate_helper(parentheses: str, num_open: int, num_close: int) -> None:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

num_open / num_close が、parentheses (引数) に含まれる parentheses の数ではなく、残り必要な数なのが気になりました。
残りの数であれば remaining や required を使う、もしくは num_open / close の定義を含まれる数、というふうにして書き直すほうが自然だと感じました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントありがとうございます。remaining などとした方がわかりやすいですね。
個人的には、実行時間が問題ないのであればコードはシンプルにしたいため、重複した情報は渡さない(引数の parentheses に含まれるかっこの数は数えたらわかる)のが好みなので、他の引数としては残りの括弧の数となるのは自然と思っていました。この辺は考え方次第でしょうか。
あとは括弧の残りが 0 になれば終了と書いた方が関数の処理の記述が n に依存しないため自然に思います。

if num_open == 0 and num_close == 0:
results.append(parentheses)
return
if num_open > 0:
generate_helper(parentheses + "(", num_open - 1, num_close)
if num_open < num_close:
generate_helper(parentheses + ")", num_open, num_close - 1)

generate_helper("", n, n)
return results
```

3分,3分,2分で3回Accept