Skip to content

Generate Parentheses

LeetCode: 22. Generate ParenthesesDifficulty: Medium Pattern: Backtracking with Constraints


Problem Statement

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.


Examples

Example 1

text
Input: n = 3
Output: ["((()))", "(()())", "(())()", "()(())", "()()()"]

Example 2

text
Input: n = 1
Output: ["()"]

Constraints

  • 1 <= n <= 8

Backtracking Tree Visualization

Generate Parentheses Tree

Key Constraints

At any point in building the string:

  1. Can add ( if: left_count < n
  2. Can add ) if: right_count < left_count

These constraints ensure we never have more ) than ( at any prefix.

text
Valid:   ( ( ) )    - at every prefix, #( >= #)
         1 2 1 0    <- balance (open parens count)

Invalid: ( ) ) (    - prefix "())" has more ) than (
         1 0 -1     <- balance goes negative = INVALID

Backtracking Template

python
def generateParenthesis(n):
    """
    Template for generating valid parentheses.

    Constraints:
    - Add '(' if left < n
    - Add ')' if right < left
    """
    result = []

    def backtrack(s, left, right):
        # BASE CASE: complete string
        if len(s) == 2 * n:
            result.append(s)
            return

        # Try adding '('
        if left < n:
            backtrack(s + '(', left + 1, right)

        # Try adding ')'
        if right < left:
            backtrack(s + ')', left, right + 1)

    backtrack('', 0, 0)
    return result

Solutions

Solution

python
def generateParenthesis(n: int) -> list:
    """
    Generate all valid parentheses combinations.

    Time Complexity: O(4^n / sqrt(n)) - Catalan number
    Space Complexity: O(n) for recursion depth
    """
    result = []

    def backtrack(s, left, right):
        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)

    backtrack('', 0, 0)
    return result


# Test
print(generateParenthesis(3))
# Output: ['((()))', '(()())', '(())()', '()(())', '()()()']
java
public List<String> generateParenthesis(int n) {
    List<String> result = new ArrayList<>();
    backtrack(result, new StringBuilder(), 0, 0, n);
    return result;
}

private void backtrack(List<String> result, StringBuilder path,
                       int open, int close, int n) {
    if (path.length() == 2 * n) {
        result.add(path.toString());
        return;
    }
    if (open < n) {
        path.append('(');
        backtrack(result, path, open + 1, close, n);
        path.deleteCharAt(path.length() - 1);
    }
    if (close < open) {
        path.append(')');
        backtrack(result, path, open, close + 1, n);
        path.deleteCharAt(path.length() - 1);
    }
}

Complexity: Time O(4^n / sqrt(n)) · Space O(n)

  • Time: The number of valid parentheses combinations is the nth Catalan number, which is bounded by O(4^n / sqrt(n)). String concatenation is O(n) per call.
  • Space: Maximum recursion depth is 2n (total characters to place), but the call stack at any time has at most O(n) frames.

Solution 2: Backtracking with List (More Efficient)

python
def generateParenthesis_list(n: int) -> list:
    """
    Use list instead of string concatenation for efficiency.

    String concatenation creates new strings; list append is O(1).

    Time Complexity: O(4^n / sqrt(n))
    Space Complexity: O(n)
    """
    result = []

    def backtrack(path, left, right):
        if len(path) == 2 * n:
            result.append(''.join(path))
            return

        if left < n:
            path.append('(')
            backtrack(path, left + 1, right)
            path.pop()

        if right < left:
            path.append(')')
            backtrack(path, left, right + 1)
            path.pop()

    backtrack([], 0, 0)
    return result


# Test
print(generateParenthesis_list(3))

Complexity: Time O(4^n / sqrt(n)) · Space O(n)

  • Time: Same Catalan number bound. Using list with append/pop is O(1) amortized per character, more efficient than string concatenation.
  • Space: Recursion depth is O(n), plus the path list stores at most 2n characters.

Solution 3: Iterative with BFS

python
from collections import deque

def generateParenthesis_bfs(n: int) -> list:
    """
    BFS approach using a queue.

    Time Complexity: O(4^n / sqrt(n))
    Space Complexity: O(4^n / sqrt(n)) for queue
    """
    result = []
    queue = deque([('', 0, 0)])  # (string, left_count, right_count)

    while queue:
        s, left, right = queue.popleft()

        if len(s) == 2 * n:
            result.append(s)
            continue

        if left < n:
            queue.append((s + '(', left + 1, right))

        if right < left:
            queue.append((s + ')', left, right + 1))

    return result


# Test
print(generateParenthesis_bfs(3))

Complexity: Time O(4^n / sqrt(n)) · Space O(4^n / sqrt(n))

  • Time: BFS explores the same search space as DFS backtracking, bounded by the Catalan number.
  • Space: The queue stores all partial strings at the current BFS level, which can grow to store all valid combinations.

Solution 4: Dynamic Programming

python
def generateParenthesis_dp(n: int) -> list:
    """
    DP approach: Build solutions from smaller subproblems.

    For n pairs, we can decompose as:
    ( [inside] ) [outside]

    where [inside] has i pairs and [outside] has n-1-i pairs.

    Time Complexity: O(4^n / sqrt(n))
    Space Complexity: O(4^n / sqrt(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):
            # j pairs inside, i-1-j pairs outside
            for inside in dp[j]:
                for outside in dp[i - 1 - j]:
                    dp[i].append('(' + inside + ')' + outside)

    return dp[n]


# Test
print(generateParenthesis_dp(3))
# Output: ['()()()', '()(())', '(())()', '(()())', '((()))']

Complexity: Time O(4^n / sqrt(n)) · Space O(4^n / sqrt(n))

  • Time: Builds solutions by combining smaller subproblems; total work is proportional to the number of valid parentheses strings.
  • Space: The dp array stores all valid combinations for each value from 0 to n, requiring space proportional to the Catalan number.

Solution 5: Closure Number

python
def generateParenthesis_closure(n: int) -> list:
    """
    Based on closure number concept.

    A valid sequence has the form: (A)B
    where A and B are valid sequences with |A| + |B| = n - 1 pairs.

    Time Complexity: O(4^n / sqrt(n))
    Space Complexity: O(4^n / sqrt(n))
    """
    if n == 0:
        return ['']

    result = []
    for c in range(n):
        for left in generateParenthesis_closure(c):
            for right in generateParenthesis_closure(n - 1 - c):
                result.append('(' + left + ')' + right)

    return result


# Test
print(generateParenthesis_closure(3))

Complexity: Time O(4^n / sqrt(n)) · Space O(4^n / sqrt(n))

  • Time: Recursively generates all combinations by decomposing into (A)B form, where |A| + |B| = n-1 pairs.
  • Space: Recursion depth can reach O(n), and intermediate results for all subproblems are stored (memoization would help repeated calls).

Complexity Analysis

The number of valid parentheses combinations is the nth Catalan number:

Cn=1n+1(2nn)=(2n)!(n+1)!n!
nCatalan Number C_n
11
22
35
414
542
6132
7429
81430

Time Complexity: O(4^n / sqrt(n)) - bounded by Catalan number Space Complexity: O(n) for recursion depth


Why These Constraints Work

Constraint 1: left < n

We can only add ( if we haven't used all n of them.

Constraint 2: right < left

We can only add ) if there's an unmatched ( to close.

Building "(()"
- Current: "("  -> left=1, right=0
- Add '(':  "((" -> left=2, right=0 (left < 3, OK)
- Add ')': "(()" -> left=2, right=1 (right < left, OK)

Invalid attempt "())" would require:
- Current: "()" -> left=1, right=1
- Add ')':  "())" -> right=2 > left=1, NOT ALLOWED!

Common Mistakes

1. Using Wrong Condition

python
# WRONG - allows invalid sequences
if right < n:  # Should be: right < left
    backtrack(s + ')', left, right + 1)

# CORRECT
if right < left:  # Only close if there's an open to match
    backtrack(s + ')', left, right + 1)

2. Missing Base Case

python
# WRONG - infinite recursion
def backtrack(s, left, right):
    if left < n:
        backtrack(s + '(', left + 1, right)
    if right < left:
        backtrack(s + ')', left, right + 1)
    # Missing: when to stop and add to result!

# CORRECT
def backtrack(s, left, right):
    if len(s) == 2 * n:  # Complete string!
        result.append(s)
        return
    # ... rest of code

3. Inefficient String Concatenation

python
# SLOW for large n - creates new strings each time
backtrack(s + '(', left + 1, right)

# FASTER - use list and join at the end
path.append('(')
backtrack(path, left + 1, right)
path.pop()

Valid Parentheses (LeetCode 20)

Problem: Given a string with (){}[], determine if the input string is valid (properly nested and matched).

Key Insight: Validation vs generation. Use stack to match opening/closing brackets.

Approach: Stack-based validation. Push opening brackets, pop and match on closing brackets.

Complexity: O(n) time, O(n) space

Link: LeetCode 20

Longest Valid Parentheses (LeetCode 32)

Problem: Find the length of the longest valid (well-formed) parentheses substring.

Key Insight: Optimization problem requiring DP or stack to track valid substring lengths.

Approach: DP: dp[i] = 2 + dp[i-1] + dp[i-dp[i-1]-2] when s[i]=')', s[i-dp[i-1]-1]='('.

Complexity: O(n) time, O(n) space

Link: LeetCode 32

Remove Invalid Parentheses (LeetCode 301)

Problem: Remove minimum number of invalid parentheses to make input valid. Return all possible results.

Key Insight: BFS to find all strings with minimum removals that are valid.

Approach: BFS level-by-level removal. First level with valid strings is the answer.

Complexity: O(2^n) time worst case, O(n) space

Link: LeetCode 301

Different Ways to Add Parentheses (LeetCode 241)

Problem: Given expression with +, -, *, return all possible results from computing in different orders.

Key Insight: Divide and conquer - split at each operator, recursively solve left and right.

Approach: For each operator, recursively compute left and right, combine all results.

Complexity: O(C_n) Catalan number time, O(C_n) space

Link: LeetCode 241


Interview Tips

  1. Start with constraints: Explain when we can add ( and )
  2. Draw the tree: Show how constraints prune invalid branches
  3. Mention Catalan numbers: Shows mathematical understanding
  4. Discuss optimization: List vs string for path building
  5. Know the DP approach: Shows versatility

Key Takeaways

  1. Two simple rules:
    • Add ( if left < n
    • Add ) if right < left
  2. No explicit backtracking needed (string immutable in Python)
  3. Catalan number gives exact count of valid combinations
  4. Use list + join for efficient string building
  5. DP decomposition: (A)B where A and B are valid sequences

Last updated: January 2026