This is a classic combinatorial problem, appearing in various forms. These strings, known as Dyck strings (see more here), consist of ( n ) X’s and ( n ) Y’s, ensuring no initial segment has more Y’s than X’s. Examples include:
1
XXXYYY, XYXXYY, XYXYXY, XXYYXY, and XXYXYY.
Similar problems include:
Generating all balanced parenthetical expressions for ( N ) pairs (i.e., this problem)
Creating all possible ways to apply a binary operator to ( N + 1 ) factors
Forming all full binary trees with ( N + 1 ) leaves
We have to keep track of open and close parentheses. Main idea is that we should add ')' only after valid '('. So, we can take 2 integer variables - open and close, to keep track of open ( and close ) parentheeis. Then our recursion follows the rule:
if open is less than n, we can add ( to the current string
if close is less than open, we can add ) to the current string
classSolution {
public List<String>generateParenthesis(int n) {
List<String> ans =new ArrayList<>();
helper(n, new StringBuilder(), 0, 0, ans);
return ans;
}
privatevoidhelper(int n, StringBuilder sb, int open, int close, List<String> ans) {
if(open == n && close == n) {
ans.add(sb.toString());
return;
}
if(open < n) {
sb.append("(");
helper(n, sb, open + 1, close, ans);
sb.setLength(sb.length()-1);
}
if(close < open) {
sb.append(")");
helper(n, sb, open, close + 1, ans);
sb.setLength(sb.length()-1);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
defgenerateParenthesis(n):
defbacktrack(S="", left=0, right=0):
if len(S) ==2* n:
result.append(S)
returnif left < n:
backtrack(S +"(", left +1, right)
if right < left:
backtrack(S +")", left, right +1)
result = []
backtrack()
return result
⏰ Time complexity: O(2^n) because at each step, we either open or close a parenthesis. That is the upper bound. But actually, if we do the counting, when we count number of valid parentheses, it is equal to catalan number, i.e. C(n) = 2n! / (n + 1)!(n!). More on it: Catalan Numbers.
classSolution {
public List<String>generateParenthesis(int n) {
Map<Integer, List<String>> memo =new HashMap<>();
memo.put(0, List.of(""));
return helper(n, memo);
}
privatevoidhelper(int n, Map<Integer, List<String>> memo) {
if (memo.containsKey(n)) {
return memo.get(n);
}
List <String> ans =new ArrayList<>();
if (n == 0) {
ans.add("");
} else {
for (int i = 0; i < n; i++) {
for (String left: dp(i, memo)) {
for (String right: dp(n - 1 - i, memo)) {
result.add("("+ left +")"+ right);
}
}
}
}
memo.put(n, result);
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
defgenerateParenthesis(n):
memo = {}
defdp(i):
if i in memo:
return memo[i]
if i ==0:
return [""]
result = []
for j in range(i):
for left in dp(j):
for right in dp(i -1- j):
result.append(f"({left}){right}")
memo[i] = result
return result
return dp(n)
We use a dynamic programming table where each entry dp[i] represents all possible combinations of i pairs of well-formed parentheses. Let dp[i] represents all well-formed combinations of i pairs of parentheses.
To get dp[i], you can think of it as placing a pair of parentheses () in every possible position around combinations of smaller pairs:
$$
dp[i] = \sum_{k=0}^{i-1} \text{( “(” + dp[k] + “)” + dp[i-1-k] )}
$$
This means placing dp[k] inside (), and then concatenating it with all valid combinations of dp[i-1-k].
publicclassSolution {
public List < String > generateParenthesis(int n) {
if (n == 0) {
returnnew ArrayList < String > () {{
add("");
}};
}
List < List < String>> dp =new ArrayList<>(n + 1);
for (int i = 0; i <= n; i++) {
dp.add(new ArrayList<>());
}
dp.get(0).add("");
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
for (String left: dp.get(j)) {
for (String right: dp.get(i - 1 - j)) {
dp.get(i).add("("+ left +")"+ right);
}
}
}
}
return dp.get(n);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
defgenerateParenthesis(n):
if n ==0:
return [""]
dp = [[] for _ in range(n +1)]
dp[0] = [""]
for i in range(1, n +1):
for j in range(i):
for left in dp[j]:
for right in dp[i -1- j]:
dp[i].append(f"({left}){right}")
return dp[n]
For n = 2, we can build the result set by inserting a new pair “()” into all gaps in each result from n = 1. For example, the only solution for n = 1 is () with 3 gaps, as shown:
1
2
3
( )
^^^ g1 g2 g3
To get the result for n = 2, we append “()” in each gap of the solutions from n = 1, leading to:
1
2
3
()() (()) ()()
^^^ g1 g2 g3
The unique result set for n = 2 is {()(), (())}.
Generally, there are m+1 gaps in a string of length m. We can construct a string with n parentheses by inserting “()” into each of the m+1 gaps in the solutions of length m for n-1.
To implement this, formulate the problem as a tree traversal problem. Starting from n = 1 as the root “()”, we traverse m+1 = 3 children corresponding to the string constructed by inserting “()” into the m+1 gaps. The nodes at level n of this tree will contain all possible strings with n pairs of parentheses. For instance:
1
2
3
4
5
( ) <- n = 1
/|\ ( ) ( ) (()) ()() <- n = 2
/|\\\ ()()() (())() ......... <- n = 3
We can perform a level order traversal using a queue and stop at level = n, at which point the queue will contain the result set for n. For more details on the level order traversal algorithm using a queue, refer to my previous note on finding the rightmost cousin of a binary tree. The following is a simple implementation of this idea.
publicstatic ArrayList<String>genParanthesis(int n) {
ArrayList<String> ans =new ArrayList<String> ();
if (n<= 0) {
return ans;
}
if (n == 1) {
ans.add("()");
return ans;
}
Queue<StringBuilder> queue =new ArrayDeque<StringBuilder> ();
Set<String> visited =new HashSet<String> ();
queue.add(new StringBuilder("()"));
int count = 1;
int level = 1;
//do a level order (BFS) trversal upto level=nwhile (!queue.isEmpty()) {
StringBuilder node = queue.poll();
String cur = node.toString();
count--;
visited.add(cur);
//at the end of level order traversal upto level n the queue will contain all the leaves at level n//which are basically all possible strings constructed with n pair of parenthesisif (level == n) {
ans.add(cur);
continue;
}
//if not reached level n yet, then do BFS level order//there are total 1+len(cur) gapsfor (int gap = 0; gap<cur.length() + 1; gap++) {
//we create a child by putting () to each of the gap positions StringBuilder child =new StringBuilder(cur).insert(gap, "()");
if (!visited.contains(child.toString())) {
queue.add(child);
visited.add(child.toString());
}
}
//end of a levelif (count == 0) {
level++;
count = queue.size();
}
}
return ans;
}