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
155 changes: 155 additions & 0 deletions 22. Generate Parentheses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# 進め方

Step1 : 問題を解く。

Step2 : 他の人のPRを参照し、コメントする。

Step3 : 3回続けてエラーが出ないように書く。ドキュメントを参照する。

# 実践

## Step1

### 思考ログ

5m くらい

```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
result = []
def helper(used_open, opening_count, parentheses):
if used_open >= n and opening_count <= 0:
result.append(parentheses)
return
if used_open < n:
helper(used_open + 1, opening_count + 1, parentheses + '(')
if opening_count > 0:
helper(used_open, opening_count - 1, parentheses + ')')

helper(0, 0, '')
return result
```

```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
result = []
stack = [(0, 0, '')]
while stack:
used_open, opening_count, parentheses = stack.pop()
if used_open >= n and opening_count <= 0:
result.append(parentheses)
if used_open < n:
stack.append((used_open + 1, opening_count + 1, parentheses + '('))
if opening_count > 0:
stack.append((used_open, opening_count - 1, parentheses + ')'))
return result
```

https://discord.com/channels/1084280443945353267/1233603535862628432/1263405280499073035

これの言っていることが分かりました。

漏れなくダブりなく網羅するためにどうするか、を考えればよい。

ループであれば、担当者は自分が取れる行動をすべて取って、それぞれ引き継ぐイメージ。

## Step2

### 同じ問題を解いた人のプルリクを見る

ざっとプルリク見た感じ、色々な書き方を試しているようだった。

https://github.com/olsen-blue/Arai60/pull/54/files

- 自分のstep1の変数名は改善の余地がありそう。
- 枝刈り。タスクを引継ぎするほうが判断するのか、される方が判断するのか。
- 今回はするほうが判断したほうが読みやすそう。
- 解法4は読めないけど理解したら得るものがありそう。
- https://github.com/hroc135/leetcode/pull/50/files
- コピーの際の計算量。
- Step1のソースはコピーが発生してますね。
- 今回の問題では気にしなくても良さそうか?
- カタラン数は常識ではないらしい。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

カタラン数は聞いたことありました?
大学受験でたまに出てくることがありますね。(知らなくても解ける程度。)

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://www.chugakujuken.com/koushi_blog/sakai/21731.html
https://ja.m.wikipedia.org/wiki/%E3%82%AB%E3%82%BF%E3%83%A9%E3%83%B3%E6%95%B0


https://github.com/nittoco/leetcode/pull/43

- この人のstep3と似ている気がする。

Step1の変数名を書き直す。

Step1では残りがあれば追加としていたが、今回は使用した分と上限を比べて、という感じにした。今回のほうがコードがすっきりしている。

```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
result = []
stack = [(0, 0, '')]
while stack:
num_open, num_close, parentheses = stack.pop()
if num_open >= n and num_close >= n:
result.append(parentheses)
if num_open < n:
stack.append((num_open + 1, num_close, f'{parentheses}('))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

文字列を連結する際、 + 演算子 1 つで済むのであれば、 + で結合しても、十分シンプルかつ読みやすいと思います。複数個になった場合は、読みやすさに応じて f 文字列に切り替えたほうがよいと思います。

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.

レビューありがとうございます。
結合する文字列がかっこなのもあいまって、+演算子のほうが読みやすく感じました。

if num_open > num_close:
stack.append((num_open, num_close + 1, f'{parentheses})'))
return result
```

今回はjoinと+は大差なさそうに思える。

> で、それに対して、「そう思っていたんですが実験した範囲では最適化が効くみたいです。」という返答は、なかなかに困って、というのも、次の疑問がわいてきます。「いつでもその最適化は行われるのか。インタープリターのバージョンに依存しないのか、たとえば、バージョンアップで最適化がなくなることはないのか。もしも、最適化がされることが保証されていないならば、そのように書いておいたとして、どういった場合に最適化されないのか。そのような仕様が仮にあるのだとしたら、そのドキュメントへのリンクをコメントで書いておいて欲しい。また、Python のバージョンが変わったときには、その最適化がされることが保証されていないならば、そのドキュメントをもう一回見て、バージョンによって仕様が変わっていないかどうかを確認するプロセスが必ず走るようにして欲しい。」で、ここまでの疑問にその場で答えられるならば、面接官は、なるほど、そうなんですね、といって、Python に詳しい人だと思うでしょう。で、仕様レベルで最適化が保証されているのだとしても、最低限コメントとして、「このような場合には最適化されることが保証される。どこどこ参照。」と書いておかないと、今後そのコードを読んでデバッグする人が、「むむ、もしかして、今回のタイムアウトの原因はここではないかな?」といって余計な実験をすることになるわけです。つまり、「あ、Python の文字列はイミュータブルなので、こうしたほうがいいですね。」は減点なしの評価で、まあ、思わずやっちゃうことあるよね、くらいの感覚です。
「この場合は最適化されることが仕様レベルで保証されているのでその旨のコメントを書き足しましょうか。」も減点はしにくいけれども、それだったら join で書き直しませんか、という気分ですね。つまり、気にしているのは、オーダーが正しく動くか、ではなくて、半年後に読んだ別の同僚が不安にならないか、環境の変化に対して頑健か、なのです。
>

```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
if n == 0:
return [""] # ここをリストにしないとエラー
result = []
for i in range(n):
for front in self.generateParenthesis(i):
for back in self.generateParenthesis(n - 1 - i):
result.append(f'({front}){back}')
return result
```

## Step3

### 3回連続で再現

```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@cache デコレータをつけると速くなりますね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

文字列を丸ごとコピーしないようにするやつを、Python にしてみました。
https://discord.com/channels/1084280443945353267/1262688866326941718/1363826801016701018

from functools import cache
from typing import List, Callable

@cache
def generate_functions_to_append_parenthesis(n: int) -> List[Callable[[List[str]], None]]:
    if n == 0:
        return [lambda sb: None]
    result = []
    for i in range(n):
        for a in generate_functions_to_append_parenthesis(i):
            for b in generate_functions_to_append_parenthesis(n - i - 1):
                def fn(sb: List[str], a=a, b=b) -> None:
                    sb.append("(")
                    a(sb)
                    sb.append(")")
                    b(sb)
                result.append(fn)
    return result

def generate_parenthesis(n: int) -> List[str]:
    funcs = generate_functions_to_append_parenthesis(n)
    result = []
    for f in funcs:
        sb = []
        f(sb)
        result.append("".join(sb))
    return result

if n == 0:
return ['']
result = []
for i in range(n):
for front in self.generateParenthesis(i):
for back in self.generateParenthesis(n - i - 1):
result.append(f'({front}){back}')
return result
```

紙に書きながらデバッグしたらなんとなくわかった。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

それほど問題にならない気もしますが、文字列の生成が何度も行われていますね。

Copy link
Copy Markdown
Owner Author

@fuga-98 fuga-98 May 26, 2025

Choose a reason for hiding this comment

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

言われてみればそうでした。
計算量の観点では文字列ではなくリストで作っていって最後にjoinしたほうが良さそうです。


```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
result = []
stack = [(0, 0, '')]
while stack:
num_opens, num_closes, parenthesis = stack.pop()
if num_opens == n and num_closes == n:
result.append(parenthesis)
continue
if num_opens < n:
stack.append((num_opens + 1, num_closes, f'{parenthesis}('))
if num_closes < num_opens:
stack.append((num_opens, num_closes + 1, f'{parenthesis})'))
return result
```

こっちは自信もって書ける。