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
131 changes: 131 additions & 0 deletions Python3/22. Generate Parentheses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
## Step 1. Initial Solution

- 再帰的にやれば行けそう
- 前のやつの全体を囲うか()を前後につけるか

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

Choose a reason for hiding this comment

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

n == 0 で [""] のほうがいいでしょう。

return ["()"]
parentheses = set()
for parenthesis in self.generateParenthesis(n-1):
parentheses.add("".join(["(", parenthesis, ")"]))
parentheses.add("".join(["()", parenthesis]))
parentheses.add("".join([parenthesis, "()"]))
return list(parentheses)
```

- と思ったが(())(())のような部分的に囲むやつが考慮できていないので失敗
Copy link

Choose a reason for hiding this comment

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

こういう風にしたいならば、はじめの括弧と対応するやつは必ずあるはずなので、
(X)Y
と分ければいいですね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

どういう実装をイメージされているんでしょうか?
再帰だとできないのかなと思ったんですが、

- Stackで(の個数と)の個数を保持しながら処理していく方針に変更

```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
parentheses = set()
parenthesis_opened_closed = [("", 0, 0)]
while parenthesis_opened_closed:
parenthesis, opened, closed = parenthesis_opened_closed.pop()
if opened == n:
new_parenthesis = parenthesis + (opened - closed) * ")"
if new_parenthesis not in parentheses:
parentheses.add(new_parenthesis)
continue
parenthesis += "("
opened += 1
for i in range(opened - closed + 1):
parenthesis_opened_closed.append((parenthesis + i * ")", opened, closed + i))

return list(parentheses)
```

- あまり直感的ではないように感じたので(の数ごとに階層を区切って処理してみる

```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
parentheses = []
parentheses_and_openings = [("", 0)]
for opened in range(1, n + 1):
new_parentheses_and_openings = []
for parenthesis, opening in parentheses_and_openings:
parenthesis += "("
opening += 1
if opened == n:
parentheses.append(parenthesis + opening * ")")
continue
for closing in range(opening + 1):
new_parentheses_and_openings.append((parenthesis + closing * ")", opening - closing))
parentheses_and_openings = new_parentheses_and_openings
return parentheses
```


### Complexity Analysis

- 時間計算量:O(n^n)
- 各層の計算量が<2k C k
- 2k C k < k^k なので大体このくらい?
- 空間計算量:O(n^n)
Copy link

Choose a reason for hiding this comment

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

これはちょっと大きすぎる見積もりです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ですね
下でもう少し正しい見積もりをしてみています


## Step 2. Alternatives

- https://github.com/akmhmgc/arai60/pull/47/files#diff-49770f8b73d94c3a97c07766ce83932c7e9fc1168820a224bd14923bdfd5c876R10
- (の追加と)の追加を各ループで行う方法
- 確かにこの方法は分かりやすい
- popしてから次の分岐に行くから同じインスタンスを参照し続けられてメモリ効率も良い
- https://github.com/shintaro1993/arai60/pull/57/files#r2485774505
- こちらでも同じことが言われていた
- https://github.com/shintaro1993/arai60/pull/57/files#diff-6c927ad20d197fb3d8c10182f5287498c084aca20e6c323cc144aa0ac3cbf4daR10
- 全体的に自分のように各作業者がfor文を回してstackに乗せるような方法は少なそう
- 一つずつstackしていく方が分かりやすいのか?
- https://github.com/fhiyo/leetcode/pull/53/files#diff-9ca14f2eb9507a65f01cd60a9947537f09b5daeede1ef28ee1d790d8d76adb56R11
- カタラン数なるものがあるらしい
- 2n C n / (n + 1)なので今回の計算量は(n+1) x Cnと書けそう
- スターリングの公式を使うと $n! = \sqrt{2 \pi n}(\frac{n}{e})^n$なので

$$
_{2n}C_n = \frac{1}{\sqrt{\pi n}} 2^{2n}
$$

- よって今回の計算量はO(n^(-1/2) *4^n)と書けそう
- https://github.com/fhiyo/leetcode/pull/53/files#diff-9ca14f2eb9507a65f01cd60a9947537f09b5daeede1ef28ee1d790d8d76adb56R257
- generatorを使って作り上げていく方法
- 他にもcacheを使うかとのトレードオフも判断していて参考になる

## Step 3. Final Solution

- 一つずつStack

```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
paren_opened_closed = [('', 0, 0)]
parens = []
while paren_opened_closed:
paren, opened, closed = paren_opened_closed.pop()
if opened == n:
parens.append(paren + (opened - closed) * ')')
continue
paren_opened_closed.append((paren + '(', opened + 1, closed))
if opened > closed:
paren_opened_closed.append((paren + ')', opened, closed + 1))
return parens
```

- generatorを使う方法
- どうやらLeetCode上ではあまり速くない上にメモリ効率も大きく変わらないよう

```python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
def generate_parens(opened: int, closed: int) -> Iterator[str]:
if opened == n:
yield ')' * (opened - closed)
return
if opened > closed:
yield from (')' + s for s in generate_parens(opened, closed + 1))
yield from ('(' + s for s in generate_parens(opened + 1 , closed))

return list(generate_parens(0, 0))
```