diff --git a/22. Generate Parentheses/firstSolution.java b/22. Generate Parentheses/firstSolution.java new file mode 100644 index 0000000..4145251 --- /dev/null +++ b/22. Generate Parentheses/firstSolution.java @@ -0,0 +1,32 @@ +class Solution { + // 再帰 + // ACだが、途中でメモ化をしてないのでかなり計算量が悪くなってTLEになった + public List generateParenthesis(int n) { + Set resultParenetheses = new HashSet<>(); + Set pastState = new HashSet<>(); + insertParenetheses(n, new StringBuilder(), resultParenetheses, pastState); + return new ArrayList<>(resultParenetheses); + } + + + private void insertParenetheses(int n, StringBuilder current, Set resultParenetheses, + Set pastState) { + if (n == 0) { + resultParenetheses.add(new String(current)); + return; + } + if (pastState.contains(new String(current))) { + return; + } + pastState.add(new String(current)); + for (int i = 0; i <= current.length(); i++) { + current.insert(i, "("); + for (int j = i + 1; j <= current.length(); j++) { + current.insert(j, ")"); + insertParenetheses(n - 1, current, resultParenetheses, pastState); + current.delete(j, j + 1); + } + current.delete(i, i + 1); + } + } +} diff --git a/22. Generate Parentheses/note.md b/22. Generate Parentheses/note.md new file mode 100644 index 0000000..99be5fa --- /dev/null +++ b/22. Generate Parentheses/note.md @@ -0,0 +1,35 @@ +# firstSolution + +アプローチとしては()を再帰的に適切な位置に挿入していき、最終的な長さになったら HashSet に加えていくというもの(重複を防ぐために Set を使っています)。 +適切な位置というのは、挿入した"("に対して、"("の位置より右側で")"が挿入されることをいみしています。(これで正しい文字列であることは保証されるので) +途中でメモ化していないことによる TLE になったので、途中の文字列についても保持して重複した再帰処理を防ぐようにしました・ + +# refactoredByMyselfSolution + +# refactoredSolution + +0, 変数名修正 +コメントいただいた箇所について修正しました。 +状態についても必要十分で変数名に含めてあげる意識が足りないなと思いました。 + +1, Queue を使った loop +コメントいただいた方法を元に以下のように考えました。 +アプローチとしては、末尾に"(",")"を挿入して Queue に保存していき、最終的に文字列の長さが 2 _ n になったら、正しい文字列かどうかを確認して正しければ List に加えるというものです。 +途中で正しい文字列か(")"のが多くならないか)をチェックしています。こちらについては最後だけ正しい文字列かをチェックする方法もあり、処理のシンプルさでは最後だけチェックのがよいですが、速度的に n が大きくなればなるほど最終的にチェックする数が増えると思うので、途中で判定して Queue に追加するものを選択したほうがはやくなると思っています。 +途中でコメントもいただき、左の数を文字列に対して保持しておけば、毎回チェックの処理を O(n)で走らせないですむので、新規に(の数を保持しておく Queue を作りました。 +時間計算量がわかりやすくて、文字列の長さ分毎回2倍増えていきますので、2^(2n)かかり、あと正しいかの判定に 2n かかるので、O(2^(2n) _ n)となるかと思います。 +空間計算量についても、Queue に入る最大の要素数は 2^(2n - 1), 要素となる文字列の長さは最大 2n なので O(2^(2n) \* n)となるかとおもいます。 + +追加いただいたコメントで、Queue は1つにまとました(管理の面でも、複数管理の場合は更新し忘れなどのリスクもあるのでまとめて管理したほうが合理的だと思いました。) +文字列と開いた(の数をまとめたクラスの命名については少し不安です(State とか Info とかしか浮かびませんでした)。 + +2, 再帰を使った方法 +再帰については、基本的に上記と考え方が同じなので、すんなり実装はできました。コメントいただいた通り、途中で条件をしっかり確認すればかなり不要な文字列のチェックを抑えることができるので、しっかり実装前や途中で不要なパターンをしっかり明確化できるのかが大事だと改めて感じました。 +また String の add に文字数分のコストがかかることを教えていただき、かなり処理時間に差が出るので StringBuilder を使ったアプローチを常に考えようと思いました。 +[確認した他の実装](https://github.com/hayashi-ay/leetcode/pull/70) + +3, Queue を使った loop の別アプローチ +複数のアプローチを思い浮かべられるとよい+ループの考えについて教えていただいたので、それをベースに違うアプローチで()を挿入した。 +挿入については"(" + ")"\*n n:0 <= n <= 閉じてない(の個数 として挿入をしていく +挿入したものは被りはない +挿入中に n = 4 で((((((((( みたいにケースでは閉じる括弧を加えると溢れてしまうケースがありえるので、それを途中で阻止するようにした。 diff --git a/22. Generate Parentheses/refactoredByMyselfSolution.java b/22. Generate Parentheses/refactoredByMyselfSolution.java new file mode 100644 index 0000000..423921d --- /dev/null +++ b/22. Generate Parentheses/refactoredByMyselfSolution.java @@ -0,0 +1,56 @@ +class Solution { + public List generateParenthesis(int n) { + List parenthesisStrings = new ArrayList<>(); + Queue parenthesisCandidate = new LinkedList<>(); + parenthesisCandidate.add(""); + + while (!parenthesisCandidate.isEmpty()) { + String current = parenthesisCandidate.poll(); + if (!isValidParenethesisInProcess(current)) { + continue; + } + if (current.length() == 2 * n) { + if (isValidParenethesis(current)) { + parenthesisStrings.add(current); + } + continue; + } + parenthesisCandidate.add(current + "("); + parenthesisCandidate.add(current + ")"); + } + return parenthesisStrings; + } + + private boolean isValidParenethesisInProcess(String string) { + int leftParenethesisCount = 0; + for (char c : string.toCharArray()) { + if (c == '(') { + leftParenethesisCount++; + } else { + leftParenethesisCount--; + } + + if (leftParenethesisCount < 0) { + return false; + } + } + return leftParenethesisCount >= 0; + } + + private boolean isValidParenethesis(String string) { + int leftParenethesisCount = 0; + for (char c : string.toCharArray()) { + if (c == '(') { + leftParenethesisCount++; + } else { + leftParenethesisCount--; + } + + if (leftParenethesisCount < 0) { + return false; + } + } + return leftParenethesisCount == 0; + } + +} diff --git a/22. Generate Parentheses/refactoredSolution.java b/22. Generate Parentheses/refactoredSolution.java new file mode 100644 index 0000000..b8a30cb --- /dev/null +++ b/22. Generate Parentheses/refactoredSolution.java @@ -0,0 +1,56 @@ +class Solution { + public List generateParenthesis(int n) { + List allParentheses = new ArrayList<>(); + Queue parenthesisCandidate = new LinkedList<>(); + parenthesisCandidate.add(""); + + while (!parenthesisCandidate.isEmpty()) { + String current = parenthesisCandidate.poll(); + if (!isValidParenethesisInProcess(current)) { + continue; + } + if (current.length() == 2 * n) { + if (isValidParenethesis(current)) { + parenthesisStrings.add(current); + } + continue; + } + parenthesisCandidate.add(current + "("); + parenthesisCandidate.add(current + ")"); + } + return parenthesisStrings; + } + + private boolean isValidParenethesisInProcess(String string) { + int notClosedLeftParenethesisCount = 0; + for (char c : string.toCharArray()) { + if (c == '(') { + notClosedLeftParenethesisCount++; + } else { + notClosedLeftParenethesisCount--; + } + + if (notClosedLeftParenethesisCount < 0) { + return false; + } + } + return notClosedLeftParenethesisCount >= 0; + } + + private boolean isValidParenethesis(String string) { + int notClosedLeftParenethesisCount = 0; + for (char c : string.toCharArray()) { + if (c == '(') { + notClosedLeftParenethesisCount++; + } else { + notClosedLeftParenethesisCount--; + } + + if (notClosedLeftParenethesisCount < 0) { + return false; + } + } + return notClosedLeftParenethesisCount == 0; + } + +} diff --git a/22. Generate Parentheses/refactoredSolution1.java b/22. Generate Parentheses/refactoredSolution1.java new file mode 100644 index 0000000..5927d04 --- /dev/null +++ b/22. Generate Parentheses/refactoredSolution1.java @@ -0,0 +1,37 @@ +class Solution { + public List generateParenthesis(int n) { + List allParentheses = new ArrayList<>(); + Queue parenthesisCandidate = new LinkedList<>(); + parenthesisCandidate.add(new ParenthesisState("", 0)); + + while (!parenthesisCandidate.isEmpty()) { + ParenthesisState currentState = parenthesisCandidate.poll(); + String curerntString = currentState.str; + int numCurrentUnClosedLeftParenthesis = currentState.numUnclosedLeftParenthesis; + + if (curerntString.length() == 2 * n) { + allParentheses.add(currentState.str); + continue; + } + if (numCurrentUnClosedLeftParenthesis < 2 * n - curerntString.length()) { + parenthesisCandidate.add(new ParenthesisState(curerntString + "(", + numCurrentUnClosedLeftParenthesis + 1)); + } + if (0 < numCurrentUnClosedLeftParenthesis) { + parenthesisCandidate.add(new ParenthesisState(curerntString + ")", + numCurrentUnClosedLeftParenthesis - 1)); + } + } + return allParentheses; + } + + class ParenthesisState { + String str; + int numUnclosedLeftParenthesis; + + ParenthesisState(String str, int numUnclosedLeftParenthesis) { + this.str = str; + this.numUnclosedLeftParenthesis = numUnclosedLeftParenthesis; + } + } +} diff --git a/22. Generate Parentheses/refactoredSolution2.java b/22. Generate Parentheses/refactoredSolution2.java new file mode 100644 index 0000000..b7d3e9c --- /dev/null +++ b/22. Generate Parentheses/refactoredSolution2.java @@ -0,0 +1,27 @@ +class Solution { + public List generateParenthesis(int n) { + List allParentheses = new ArrayList<>(); + makeCombination(n, allParentheses, new StringBuilder(), 0); + return allParentheses; + } + + private void makeCombination(int n, List allParentheses, StringBuilder current, + int unclosedLeftParentheses) { + if (current.length() == 2 * n) { + allParentheses.add(current.toString()); + return; + } + // 残りの挿入可能文字数が、閉じていない(の数より多い時は(を挿入 + if (unclosedLeftParentheses < 2 * n - current.length()) { + current.append("("); + makeCombination(n, allParentheses, current, unclosedLeftParentheses + 1); + current.deleteCharAt(current.length() - 1); + } + // 閉じていない(があるので)を挿入 + if (0 < unclosedLeftParentheses) { + current.append(")"); + makeCombination(n, allParentheses, current, unclosedLeftParentheses - 1); + current.deleteCharAt(current.length() - 1); + } + } +} diff --git a/22. Generate Parentheses/refactoredSolution3.java b/22. Generate Parentheses/refactoredSolution3.java new file mode 100644 index 0000000..913a575 --- /dev/null +++ b/22. Generate Parentheses/refactoredSolution3.java @@ -0,0 +1,28 @@ +class Solution { + public List generateParenthesis(int n) { + List allParentheses = new ArrayList<>(); + Queue> parenthesisCandidate = new LinkedList<>(); + parenthesisCandidate.add(new Pair<>("", 0)); + + while (!parenthesisCandidate.isEmpty()) { + Pair currentState = parenthesisCandidate.poll(); + String curerntString = currentState.getKey(); + int numCurrentUnClosedLeftParenthesis = currentState.getValue(); + + if (curerntString.length() == 2 * n && numCurrentUnClosedLeftParenthesis == 0) { + allParentheses.add(currentState.getKey()); + continue; + } + for (int i = 0; i <= numCurrentUnClosedLeftParenthesis + 1; i++) { + if (2 * n < curerntString.length() + 1 + i) { + continue; + } + String closeParenthesis = ")".repeat(i); + String nextCandidate = curerntString + "(" + closeParenthesis; + parenthesisCandidate.add(new Pair(nextCandidate, + numCurrentUnClosedLeftParenthesis + 1 - i)); + } + } + return allParentheses; + } +}