Problem
Implement an algorithm to print all valid (i.e., properly opened and closed) combinations of n-pairs of parentheses.
OR
Given n
pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
Follow up
Make sure the returned list of strings are sorted.
Examples
Example 1:
Input: n = 3
Output: ["((()))","(()())","(())()","()(())","()()()"]
Example 2:
Input: n = 1
Output: ["()"]
Solution
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:
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
Method 1 - Backtracking
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 thann
, we can add(
to the current string - if
close
is less thanopen
, we can add)
to the current string
Here is how the recursion tree will look:
graph TD A[" "] A --> |"("| O["("] O --> |"("| O2["(("] O --> |")"| OC["()"] O2 --> |"("| O3["((("] O3 --> |")"| O3C["((()"] O3C --> |")"| O3C2["((())"] O3C2 --> |")"| O3C3["((()))"]:::answer OC --> |"("| OCO["()("] OCO --> |"("| OCO2["()(("] OCO2 --> |")"| OCO2C["()(()"] OCO2C --> |")"| OCO2C2["()(())"]:::answer O2 --> |")"| O2C["(()"] O2C --> |"("| O2CO["(()("] O2CO --> |")"| O2COC["(()()"] O2COC --> |")"| O2COC2["(()())"]:::answer OCO --> |")"| OCOC["()()"] OCOC --> |"("| OCOCO["()()("] OCOCO --> |"("| OCOCOC["()()()"]:::answer O2C --> |")"| O2C2["(())"] O2C2 --> |"("| O2C2O["(())("] O2C2O --> |"("| O2C2O2["(())()"]:::answer classDef answer fill:green,stroke:#333,stroke-width:2px;
Code
Java
Using String
class Solution {
public List<String> generateParenthesis(int n) {
List<String> ans = new ArrayList<String>();
if (n == 0) {
return ans;
}
helper(n, "", 0, 0, ans);
return ans;
}
private void helper(int n, String current, int open, int close, List<String> ans) {
if (current.length() == n * 2) {
ans.add(current);
}
if (open < n) {
helper(n, current + "(", open + 1, close, ans);
}
if (close < open) {
helper(n, current + ")", open, close + 1, ans);
}
}
}
Using StringBuilder
class Solution {
public List<String> generateParenthesis(int n) {
List<String> ans = new ArrayList<>();
helper(n, new StringBuilder(), 0, 0, ans);
return ans;
}
private void helper(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);
}
}
}
Python
def generateParenthesis(n):
def backtrack(S="", left=0, right=0):
if len(S) == 2 * n:
result.append(S)
return
if left < n:
backtrack(S + "(", left + 1, right)
if right < left:
backtrack(S + ")", left, right + 1)
result = []
backtrack()
return result
Complexity
- ⏰ 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. - 🧺 Space complexity:
O(n)
assuming recursion stack
Method 2 - Top Down DP
Here’s how you can implement this approach:
Recursive Function with Memoization:
- Use a dictionary (or a map) to store previously computed results for specific numbers of pairs.
- Recursively generate combinations by placing pairs in every valid position.
- Memoize results to avoid recomputation.
Base Case:
- When (n = 0), the only result is an empty string.
Try to visualize the below code with memo map:
memo[0] = [""]
memo[1] = ["()"]
memo[2] = ["()()", "(())"]
memo[3] = ["((()))", "(()())", "(())()", "()(())", "()()()"]
Code
Java
class Solution {
public List<String> generateParenthesis(int n) {
Map<Integer, List<String>> memo = new HashMap<>();
memo.put(0, List.of(""));
return helper(n, memo);
}
private void helper(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;
}
}
Python
def generateParenthesis(n):
memo = {}
def dp(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)
Method 3 - Bottom up DP
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.
Recurrence relation
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]
.
Code
Java
public class Solution {
public List < String > generateParenthesis(int n) {
if (n == 0) {
return new 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);
}
}
Python
def generateParenthesis(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]
Method 4 - Level Order Traversal
Let’s start with the base cases:
- n = 0: The result set is empty (no solution).
- n = 1: The result set is {()}.
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:
( )
^ ^ ^
g1 g2 g3
To get the result for n = 2, we append “()” in each gap of the solutions from n = 1, leading to:
()() (()) ()()
^ ^ ^
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:
( ) <- 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.
Code
Java
public static 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=n
while (!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 parenthesis
if (level == n) {
ans.add(cur);
continue;
}
//if not reached level n yet, then do BFS level order
//there are total 1+len(cur) gaps
for (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 level
if (count == 0) {
level++;
count = queue.size();
}
}
return ans;
}