-
Notifications
You must be signed in to change notification settings - Fork 0
22. Generate Parentheses #50
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,233 @@ | ||
問題: https://leetcode.com/problems/generate-parentheses/description/ | ||
|
||
### Step 1 | ||
- 手でやることをコードで再現したらできた。嬉しい。成長を感じる | ||
- 手でやったところ、n=i で生成された parenthesis を基に n=i+1 のものを生成するが、 | ||
どうしても重複なく生成する方法を思いつかなかったので、map に格納していくことにした | ||
- "(())" を基に次の parenthesis を生成するなら、頭と末尾に "()" を付けたやつ("()(())", "(())()")と、 | ||
'(' の後ろに "()" を付けたやつ("(()())", "((()))")を生成できる。 | ||
- Go の string はイミュータブルなのでコピーするコードを書かなくて済んだ。 | ||
- 時間計算量と空間計算量の求め方がわからん | ||
- 一応立てた漸化式はこんな感じ | ||
- T(n) = T(n-1) + p(n-1) × 2(n-1) | ||
- p(i) は n=i のときに生成されたユニークな parenthesis の個数 | ||
- カタラン数というもので抑えられるらしい | ||
- https://github.com/frinfo702/software-engineering-association/pull/10#discussion_r1881386807 | ||
- 他の解法も考えてみる。再帰に直すことはできそう。他のロジックは思いつかない | ||
|
||
```Go | ||
func generateParenthesis(n int) []string { | ||
if n == 0 { | ||
return []string{} | ||
} | ||
parenthesis := map[string]struct{}{"()": {}} | ||
for i := 2; i <= n; i++ { | ||
nextParenthesis := make(map[string]struct{}) | ||
for p := range parenthesis { | ||
newParenthesis := "()" + p | ||
nextParenthesis[newParenthesis] = struct{}{} | ||
for j := range len(p) { | ||
if p[j] == '(' { | ||
newParenthesis = p[:j+1] + "()" + p[j+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. L27、28とL35、36をなくす代わりに、forループの範囲を伸ばして、最初か最後、もしくはp[j] = '('なら'()'を挿入する、というのを考えましたが、今の実装の方がむしろ簡潔そうですね。 |
||
nextParenthesis[newParenthesis] = struct{}{} | ||
} | ||
} | ||
newParenthesis = p + "()" | ||
nextParenthesis[newParenthesis] = struct{}{} | ||
} | ||
parenthesis = nextParenthesis | ||
} | ||
result := []string{} | ||
for k := range parenthesis { | ||
result = append(result, k) | ||
} | ||
return result | ||
} | ||
``` | ||
|
||
### Step 2 | ||
#### 2a | ||
- step1 の修正 | ||
- Go1.23 からイテレータが使えるようになったので、使ってみる(map.Keys) | ||
|
||
```Go | ||
func generateParenthesis(n int) []string { | ||
if n == 0 { | ||
return []string{} | ||
} | ||
parenthesis := map[string]struct{}{"()": {}} | ||
for i := 2; i <= n; i++ { | ||
nextParenthesis := make(map[string]struct{}) | ||
for p := range maps.Keys(parenthesis) { | ||
nextParenthesis["()"+p] = struct{}{} | ||
nextParenthesis[p+"()"] = struct{}{} | ||
for j := range len(p) { | ||
if p[j] == '(' { | ||
newP := p[:j+1] + "()" + p[j+1:] | ||
nextParenthesis[newP] = struct{}{} | ||
} | ||
} | ||
} | ||
parenthesis = nextParenthesis | ||
} | ||
result := []string{} | ||
for p := range maps.Keys(parenthesis) { | ||
result = append(result, p) | ||
} | ||
return result | ||
} | ||
``` | ||
|
||
#### 2b | ||
- 2a を再帰に直した | ||
|
||
```Go | ||
func generateParenthesis(n int) []string { | ||
if n == 0 { | ||
return []string{} | ||
} | ||
if n == 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. 一応n == 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. n == 0 のときに空の配列を返しているため、n == 1 の処理がないと for _, p := range previousParenthesis のループが一度も回らないです。n == 0 のときに 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. 確かにそうですね(まだ脳内シミュレーション能力が足りない、、) |
||
return []string{"()"} | ||
} | ||
previousParenthesis := generateParenthesis(n - 1) | ||
parenthesisSet := make(map[string]struct{}) | ||
for _, p := range previousParenthesis { | ||
parenthesisSet["()"+p] = struct{}{} | ||
parenthesisSet[p+"()"] = struct{}{} | ||
for i := range len(p) { | ||
if p[i] == '(' { | ||
newP := p[:i+1] + "()" + p[i+1:] | ||
parenthesisSet[newP] = struct{}{} | ||
} | ||
} | ||
} | ||
parenthesis := []string{} | ||
for p := range maps.Keys(parenthesisSet) { | ||
parenthesis = append(parenthesis, p) | ||
} | ||
return parenthesis | ||
} | ||
``` | ||
|
||
#### 2c | ||
- https://discord.com/channels/1084280443945353267/1201211204547383386/1230529256358940722 | ||
- 括弧を前から構築していっている | ||
- なるほど、')' は '(' の個数の方が多いときにしか追加できないことを利用している | ||
|
||
```Go | ||
func generateParenthesis(n int) []string { | ||
type parenthesisBuilder struct { | ||
parenthesis string | ||
leftCount int // leftCount indicates the number of '(' included in parenthesis | ||
rightCount int | ||
} | ||
stack := []parenthesisBuilder{{"", 0, 0}} | ||
result := []string{} | ||
for len(stack) > 0 { | ||
top := stack[len(stack)-1] | ||
stack = stack[:len(stack)-1] | ||
parenthesis, leftCount, rightCount := top.parenthesis, top.leftCount, top.rightCount | ||
if leftCount == n && rightCount == n { | ||
result = append(result, parenthesis) | ||
continue | ||
} | ||
if leftCount < n { | ||
stack = append(stack, parenthesisBuilder{parenthesis + "(", leftCount + 1, rightCount}) | ||
} | ||
if rightCount < leftCount { | ||
stack = append(stack, parenthesisBuilder{parenthesis + ")", leftCount, rightCount + 1}) | ||
} | ||
} | ||
return result | ||
} | ||
``` | ||
|
||
#### 2d | ||
- https://github.com/olsen-blue/Arai60/pull/54/files#diff-f09bc4caa2343f04592a1bdcc48b3303c4f95bfa6f334c126dc596850115384eR244 | ||
- これぞバックトラック | ||
- parenthesis を rune 型のスライスで管理することによって、イミュータブルな string より計算コストを削減できた | ||
(毎回コピーする必要がなくなった) | ||
|
||
```Go | ||
func generateParenthesis(n int) []string { | ||
result := []string{} | ||
parenthesis := []rune{} | ||
|
||
var generateParenthesisHelper func(int, int) | ||
generateParenthesisHelper = func(leftCount, rightCount int) { | ||
if leftCount == n && rightCount == n { | ||
result = append(result, string(parenthesis)) | ||
return | ||
} | ||
if leftCount < n { | ||
parenthesis = append(parenthesis, '(') | ||
generateParenthesisHelper(leftCount+1, rightCount) | ||
parenthesis = parenthesis[:len(parenthesis)-1] | ||
} | ||
if rightCount < leftCount { | ||
parenthesis = append(parenthesis, ')') | ||
generateParenthesisHelper(leftCount, rightCount+1) | ||
parenthesis = parenthesis[:len(parenthesis)-1] | ||
} | ||
} | ||
|
||
generateParenthesisHelper(0, 0) | ||
return result | ||
} | ||
``` | ||
|
||
#### 2e | ||
- https://github.com/olsen-blue/Arai60/pull/54/files#diff-f09bc4caa2343f04592a1bdcc48b3303c4f95bfa6f334c126dc596850115384eR303 | ||
- おお!感動 | ||
- 問題の分解の仕方を変えるだけでこんなにすっきり書けてしまうとは | ||
- https://sentry.io/answers/concatenating-strings-in-go/ | ||
- strings.Builder で用意したバッファに WriteString で書き込むとコピーせずに string を結合できるらしい | ||
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. strings.Builder でコピーがされないので速いというのは一般論としては正しいです。 そうすると、高階関数を使うことになるでしょう。 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. func generateFunctionsToAppendParenthesis(n int) []func(*strings.Builder) {
if n == 0 {
return []func(*strings.Builder){func(sb *strings.Builder) {}}
}
result := []func(*strings.Builder){}
for i := 0; i < n; i++ {
aFuncs := generateFunctionsToAppendParenthesis(i)
bFuncs := generateFunctionsToAppendParenthesis(n - i - 1)
for _, a := range aFuncs {
for _, b := range bFuncs {
aCopy := a
bCopy := b
fn := func(sb *strings.Builder) {
sb.WriteString("(")
aCopy(sb)
sb.WriteString(")")
bCopy(sb)
}
result = append(result, fn)
}
}
}
return result
}
func executeFunctions(funcs []func(*strings.Builder)) []string {
result := make([]string, 0, len(funcs))
for _, f := range funcs {
var sb strings.Builder
f(&sb)
result = append(result, sb.String())
}
return result
}
func generateParenthesis(n int) []string {
funcs := generateFunctionsToAppendParenthesis(n)
results := executeFunctions(funcs)
return results
} 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. 元のコードだと、 |
||
- 可読性は落ちたと思う | ||
|
||
```Go | ||
func generateParenthesis(n int) []string { | ||
if n == 0 { | ||
return []string{""} | ||
} | ||
result := []string{} | ||
for i := range n { | ||
for _, a := range generateParenthesis(i) { | ||
for _, b := range generateParenthesis(n - i - 1) { | ||
var p strings.Builder | ||
p.WriteString("(") | ||
p.WriteString(a) | ||
p.WriteString(")") | ||
p.WriteString(b) | ||
result = append(result, p.String()) | ||
} | ||
} | ||
} | ||
return result | ||
} | ||
``` | ||
|
||
### Step 3 | ||
|
||
```Go | ||
func generateParenthesis(n int) []string { | ||
if n == 0 { | ||
return []string{""} | ||
} | ||
result := []string{} | ||
for i := range n { | ||
for _, a := range generateParenthesis(i) { | ||
for _, b := range generateParenthesis(n - i - 1) { | ||
var p strings.Builder | ||
p.WriteString("(") | ||
p.WriteString(a) | ||
p.WriteString(")") | ||
p.WriteString(b) | ||
result = append(result, p.String()) | ||
} | ||
} | ||
} | ||
return result | ||
} | ||
``` | ||
|
||
### CS |
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.
自分のメモ代わりに書いている文章にコメントするのも変ですが、これ最初のカッコと最後のカッコは、(A, B)でペアという意味のカッコですね(やや混乱しました)