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
188 changes: 188 additions & 0 deletions 22. Generate Parenthesis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
### Step1

- まずはバックトラック
- カタラン数のため、再帰の限界は大丈夫なはず?
- returnはなくても一応大丈夫だが、合ったほうが読み手に推理させず親切か

```python

class Solution:
def generateParenthesis(self, n: int) -> List[str]:
parenthesis = []
parenthesis_length_all = 2 * n

def generate_parenthesis_after(open_count_so_far, close_count_so_far):
if parenthesis_length_all == open_count_so_far + close_count_so_far:
if open_count_so_far == close_count_so_far:
yield ''.join(parenthesis)
return
if open_count_so_far < n:
parenthesis.append('(')
yield from generate_parenthesis_after(open_count_so_far + 1, close_count_so_far)
parenthesis.pop()
if close_count_so_far < open_count_so_far:
parenthesis.append(')')
yield from generate_parenthesis_after(open_count_so_far, close_count_so_far + 1)
parenthesis.pop()

return list(generate_parenthesis_after(0, 0))
```

- バックトラックよりこういう単純な再帰の方が好きかも
- parenthesisという変数があっちこっちで動くのはややわかりにくい
- 上は再帰に入る前に違反しないようにcheckしているが、違反した後にreturnしてみた。ややオーバーヘッドがあるかもだが、こっちの方がやや好み?
- yieldされる条件のif open_count_so_far == close_count_so_far:はなくてもいいが、入れたほうが読みやすいと思い入れた

```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
parenthesis_length_all = 2 * n

def generate_parenthesis_after(open_count_so_far, close_count_so_far):
if open_count_so_far + close_count_so_far == parenthesis_length_all:
if open_count_so_far == close_count_so_far:
yield ''
return
if open_count_so_far > n:
return
if close_count_so_far > open_count_so_far:
return
for next_parenthesis in generate_parenthesis_after(open_count_so_far + 1, close_count_so_far):
yield '(' + next_parenthesis
for next_parenthesis in generate_parenthesis_after(open_count_so_far, close_count_so_far + 1):
yield ')' + next_parenthesis

return list(generate_parenthesis_after(0, 0))
```

## Step2

https://github.com/fhiyo/leetcode/pull/53/files

- (, )は英語でleft, rightともいうのか。ただ、left, right1単語だけだと、今見てるindexよりleftかどうかとわからなくなりそう

Choose a reason for hiding this comment

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

自分はopen closeを使いました

- ( + ) * できるだけ を一つの単位として引き継ぐ方法もある
- L166のlengthだけの確認しばらく後に、L173~174の残りopenの確認があってOKと確認するのはやや戸惑った
- 下の自分のコードは、parenthesisを引数で回してもよかったかね。まあどちらでもいいかなあ
- for right_count_to_append in range(left_count_unclosed + 2): で+2がいきなり出てくるのはわかりにくいかもと思ったが、特に解決策はない

```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
parenthesis = ""

def generate_parentheses_helper(left_count_unclosed, rest_right_count):
Copy link

@Yoshiki-Iwasa Yoshiki-Iwasa Jan 18, 2025

Choose a reason for hiding this comment

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

unclosed_left_countのほうが意味がわかると思います

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かに修飾語は前にあったほうがわかりやすいですね

nonlocal parenthesis
if rest_right_count == 0 and left_count_unclosed == 0:
yield parenthesis
return
original_length = len(parenthesis)
if left_count_unclosed < rest_right_count:
for right_count_to_append in range(left_count_unclosed + 2):
parenthesis += '(' + ')' * right_count_to_append
yield from generate_parentheses_helper(left_count_unclosed + 1 - right_count_to_append, rest_right_count - right_count_to_append)
parenthesis = parenthesis[:original_length]

return list(generate_parentheses_helper(0, n))
```

https://github.com/rossy0213/leetcode/pull/27/files

- バックトラックされる文字列をnonlocalではなく引数に入れているが、引数が多いと多いで管理が難しそう。ただnonlocalもそれはそれで微妙かも

https://github.com/rihib/leetcode/pull/11/files

- stackという変数で文字列を管理するのもちょっと変わった気もするが、まあアリなのかな

https://github.com/wf9a5m75/leetcode3/pull/2/files

- 少し変わったやり方だが、これはこれでアリかも?
- 「n個目まで見た時に有効なかっこ列である」ような最小のnについて場合わけしている、と解釈できる
- 下のコードは少しネストが深いかな。for inside_count in range(n):より下の部分を関数化するか迷ったが、流石にくどい?

```python

def generator_cache(f):
cache = {}
kargs_mark = (object(),)

def copy_generator_for_cache_and_return(*args, **kargs):
key = args + kargs_mark + tuple(kargs.items())
Copy link

Choose a reason for hiding this comment

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

kargs.items() 順序が入れ替わる可能性があるが、その場合でも本来同じものなのではないでしょうか。

Copy link
Owner Author

Choose a reason for hiding this comment

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

そうでした

if key not in cache:
cache[key] = f(*args, **kargs)
cache[key], result = tee(cache[key])
return result

return copy_generator_for_cache_and_return

class Solution:
def generateParenthesis(self, n: int) -> List[str]:

@generator_cache
def generate_parenthesis_helper(n):
if n == 0:
yield ""
for inside_count in range(n):
for inside in generate_parenthesis_helper(inside_count):
for outside in generate_parenthesis_helper(n - 1 - inside_count):
yield '(' + inside + ')' + outside

return list(generate_parenthesis_helper(n))
```

- cacheは呼び出し元に破壊されるリスクはある。なるほど。
- 上の自分のコードの場合は、関数が呼ばれるたびに新たに内部の関数のオブジェクトが作られるから大丈夫
- generatorをcacheする方法

https://github.com/goto-untrapped/Arai60/pull/11/files

- 全部作ってから後でvalidか確認する方法もあり(ちょっと無駄だが)
- (の閉じてない数は、left_countではないような?(left_unclosed_count?)
- 個人的には2 * nをlength_to_makeとか変数定義した方がわかりやすく感じるが、好みの問題か

https://github.com/shining-ai/leetcode/pull/53/files

- remainの個数を辞書にして表現するStep1はわかりやすいと思った
- ループに直してみる

```python

class Solution:
def generateParenthesis(self, n: int) -> List[str]:
stack = [("", 0, 0)]
result = []
while stack:
parenthesis, left_count, right_count = stack.pop()
if left_count == n and right_count == n:
result.append(parenthesis)
continue
if left_count < n:
stack.append((parenthesis + '(', left_count + 1, right_count))
if left_count > right_count:
stack.append((parenthesis + ')', left_count, right_count + 1))
return result
```

https://github.com/frinfo702/software-engineering-association/pull/10/files

- currentはあまり意味なさそう?backtrackはぎり許容できる関数名

### Step3

- かっこ列について、やはり再帰の直前、直後でpopやappendしたり、nonlocalで管理するよりは、引数で管理するのがわかりやすい
Copy link

Choose a reason for hiding this comment

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

Generator 楽しいですが、オーソドックスにこれが一番でしょうね。

Generator のいいところとして、すべて使わないときに計算を省略できるとか中間のメモリーが軽くなることがあるなどはあるでしょう。


```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
result = []
def generate_parenthesis_helper(parenthesis, left_count, right_count):
if left_count == n and right_count == n:
result.append(parenthesis)
return
if left_count < n:
generate_parenthesis_helper(parenthesis + '(', left_count + 1, right_count)
if right_count < left_count:
generate_parenthesis_helper(parenthesis + ')', left_count, right_count + 1)

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