diff --git a/22. Generate Parenthesis.md b/22. Generate Parenthesis.md new file mode 100644 index 0000000..09f5f11 --- /dev/null +++ b/22. Generate Parenthesis.md @@ -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かどうかとわからなくなりそう +- ( + ) * できるだけ を一つの単位として引き継ぐ方法もある +- 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): + 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()) + 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で管理するよりは、引数で管理するのがわかりやすい + +```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 +```