diff --git a/22.Generate-Parentheses/benchmark_sol2.py b/22.Generate-Parentheses/benchmark_sol2.py new file mode 100644 index 0000000..fc917b3 --- /dev/null +++ b/22.Generate-Parentheses/benchmark_sol2.py @@ -0,0 +1,47 @@ +import timeit +from pathlib import Path +from typing import List + +DIR = Path(__file__).resolve().parent + + +def load_solution_class(path: Path) -> type: + src = path.read_text() + if "from typing" not in src.split("\n", 5)[:5]: + src = "from typing import List\n" + src + ns: dict = {"__name__": f"bench_{path.stem}", "List": List} + exec(compile(src, str(path), "exec"), ns) + return ns["Solution"] + + +def main() -> None: + Sol2 = load_solution_class(DIR / "sol2.py") + Sol2Revised = load_solution_class(DIR / "sol2_revised.py") + + cases = [8, 10, 12, 13] + print("-" * 60) + + for n in cases: + number = 5 if n <= 10 else 2 if n == 12 else 1 + stmt1 = f"Solution().generateParenthesis({n})" + t1 = timeit.timeit( + stmt1, + number=number, + globals={"Solution": Sol2}, + ) + t2 = timeit.timeit( + stmt1, + number=number, + globals={"Solution": Sol2Revised}, + ) + ratio = t2 / t1 if t1 > 0 else float("inf") + + print( + f"n={n:2d} number={number:2d} " + f"sol2: {t1 * 1000:8.2f} ms sol2_revised: {t2 * 1000:8.2f} ms " + f"(revised / sol2 = {ratio:.3f})" + ) + + +if __name__ == "__main__": + main() diff --git a/22.Generate-Parentheses/memo.md b/22.Generate-Parentheses/memo.md new file mode 100644 index 0000000..e560ce7 --- /dev/null +++ b/22.Generate-Parentheses/memo.md @@ -0,0 +1,58 @@ +# 22.Generate Parentheses + +最初にDFSを思いついたので解く: sol1.py + +バックトラック: sol2.py + +### コメント集 +> Python での計算時間の見積もり方。ネイティブコードで走るか否か。 + +joinはCPythonで書かれているんだった + +> 計算量はあくまでも極限での振る舞いなので、計算量を使って計算時間を見積もるのが大事です。 + +> また、速度が速いかどうかは、普通コーディングにおいてそれほどプライオリティーが高くないです。 + +> Generator をキャッシュする方法についてのコメント。 + +Generatorをキャッシュするデコレータを作ろうとしたけど、kargsがキーの挿入順に依存してしまっている + +https://github.com/olsen-blue/Arai60/pull/54#discussion_r2027288220 + +> これは解の空間をどう分けるか、分類するかの考え方で網羅的に分類できていればなんでもいいのです。 + +分類の網羅、は覚えておこう + +https://github.com/hroc135/leetcode/pull/50#discussion_r2052246310 + + +高階関数 + +https://github.com/fuga-98/arai60/pull/52#discussion_r2137961581 +> 文字列を丸ごとコピーしないようにするやつを、Python にしてみました。 + +https://github.com/hroc135/leetcode/pull/50 + +strings.Builderに書き込む関数を再帰で組み合わせている +再帰は関数の合成、ということだろうか +文字列のコピーがないので高速 +自分では書けそうにないが + + + + +https://github.com/olsen-blue/Arai60/pull/54#discussion_r2027288220 + +これは面白い解法だ。なるほど、最初の左かっこに対応する右かっこの位置で場合分けをしている: sol3.py +- メモ化再帰に直した + +### 追記 +sol2 と sol2_revisedを比較すると速さはそれほど変わらない、というかrevisedの方が少し遅かった(geminiに手伝ってもらいました) + +答えの本数だけlistからjoinでstrに毎回変換し、文字列の更新よりもオーバヘッドが大きいためだと考えられる + +----------------------------------------------------------- +n= 8 number= 5 sol2: 8.40 ms sol2_revised: 10.00 ms (revised / sol2 = 1.190) +n=10 number= 5 sol2: 103.18 ms sol2_revised: 116.89 ms (revised / sol2 = 1.133) +n=12 number= 2 sol2: 520.99 ms sol2_revised: 594.95 ms (revised / sol2 = 1.142) +n=13 number= 1 sol2: 933.25 ms sol2_revised: 1076.60 ms (revised / sol2 = 1.154) diff --git a/22.Generate-Parentheses/sol1.py b/22.Generate-Parentheses/sol1.py new file mode 100644 index 0000000..bd1df1c --- /dev/null +++ b/22.Generate-Parentheses/sol1.py @@ -0,0 +1,18 @@ +class Solution: + def generateParenthesis(self, n: int) -> List[str]: + if n == 0: + return [""] + + parentheses = [] + frontier = [("(", 1, 0)] + while frontier: + parenthesis, left_used, right_used = frontier.pop() + if right_used == n: + parentheses.append(parenthesis) + continue + if left_used < n: + frontier.append((parenthesis + "(", left_used + 1, right_used)) + if left_used > right_used: + frontier.append((parenthesis + ")", left_used, right_used + 1)) + + return parentheses diff --git a/22.Generate-Parentheses/sol2.py b/22.Generate-Parentheses/sol2.py new file mode 100644 index 0000000..1b09714 --- /dev/null +++ b/22.Generate-Parentheses/sol2.py @@ -0,0 +1,22 @@ +class Solution: + def generateParenthesis(self, n: int) -> List[str]: + if n == 0: + return [""] + + parentheses = [] + + def generate_parenthesis_from(parenthesis, left_used, right_used): + assert left_used >= right_used + if right_used == n: + parentheses.append(parenthesis) + return + if left_used < n: + parenthesis += "(" + generate_parenthesis_from(parenthesis, left_used + 1, right_used) + parenthesis = parenthesis[:-1] + if right_used < left_used: + parenthesis += ")" + generate_parenthesis_from(parenthesis, left_used, right_used + 1) + + generate_parenthesis_from("", 0, 0) + return parentheses diff --git a/22.Generate-Parentheses/sol2_revised.py b/22.Generate-Parentheses/sol2_revised.py new file mode 100644 index 0000000..683963c --- /dev/null +++ b/22.Generate-Parentheses/sol2_revised.py @@ -0,0 +1,25 @@ +class Solution: + def generateParenthesis(self, n: int) -> List[str]: + if n == 0: + return [""] + + results = [] + + def generate_parenthesis_from( + prefix: List[str], left_used: int, right_used: int + ): + assert left_used >= right_used + if right_used == n: + results.append("".join(prefix)) + return + if left_used < n: + prefix.append("(") + generate_parenthesis_from(prefix, left_used + 1, right_used) + prefix.pop() + if right_used < left_used: + prefix.append(")") + generate_parenthesis_from(prefix, left_used, right_used + 1) + prefix.pop() + + generate_parenthesis_from([], 0, 0) + return results diff --git a/22.Generate-Parentheses/sol3.py b/22.Generate-Parentheses/sol3.py new file mode 100644 index 0000000..54ffe9f --- /dev/null +++ b/22.Generate-Parentheses/sol3.py @@ -0,0 +1,15 @@ +import functools + + +class Solution: + @functools.cache + def generateParenthesis(self, n: int) -> List[str]: + # Valid parentheses must be written in the form of (A)B, where A and B are valid parentheses uniquely. + if n == 0: + return [""] + result = [] + for i in range(n): + for A in self.generateParenthesis(i): + for B in self.generateParenthesis(n - 1 - i): + result.append("({}){}".format(A, B)) + return result diff --git a/22.Generate-Parentheses/sol3_revised.py b/22.Generate-Parentheses/sol3_revised.py new file mode 100644 index 0000000..56e3095 --- /dev/null +++ b/22.Generate-Parentheses/sol3_revised.py @@ -0,0 +1,15 @@ +import functools + + +class Solution: + @functools.cache + def generateParenthesis(self, n: int) -> List[str]: + # Valid parentheses must be written in the form of (A)B, where A and B are valid parentheses uniquely. + if n == 0: + return [""] + results = [] + for i in range(n): + for A in self.generateParenthesis(i): + for B in self.generateParenthesis(n - 1 - i): + results.append(f"(A)B") + return results