Loading content...
Every programmer has encountered a syntax error caused by mismatched parentheses. The frustrating "unexpected ) " or "missing (" errors that can take minutes to track down in deeply nested code. But have you ever wondered: how many valid ways are there to arrange n pairs of parentheses?
The Generate Parentheses problem (LeetCode 22) asks us to produce all valid combinations of n pairs of parentheses. For n=3, there are exactly 5 valid combinations—can you list them all before reading further?
This problem is a beautiful application of backtracking because it introduces balance constraints—a new type of validity condition that's more abstract than the character-matching we saw in Word Search or the sum-matching in Combination Sum. Here, validity is about maintaining a running invariant: at any point, we must have used at least as many open parentheses as close parentheses.
By the end of this page, you will master the Generate Parentheses problem, understand the balance constraint that defines validity, implement efficient backtracking solutions, analyze why the output size follows the Catalan numbers, and recognize how this pattern applies to generating other balanced structures.
The Generate Parentheses Problem (LeetCode 22):
Given n pairs of parentheses, write a function to generate all combinations of well-formed (valid) parentheses.
What Makes Parentheses "Valid"?
A string of parentheses is valid if and only if:
( and ) characters( is ≥ the count of )Intuitively: every ) must have a matching ( that came before it.
Examples:
| n | Valid Combinations | Count |
|---|---|---|
| 1 | () | 1 |
| 2 | ()(), (()) | 2 |
| 3 | ()()(), (())(), ()(()), (()()), ((())) | 5 |
| 4 | 14 combinations | 14 |
The counts follow a famous sequence: 1, 2, 5, 14, 42, 132, ... These are the Catalan numbers, which appear throughout combinatorics.
n = 3["((()))","(()())","(())()","()(())","()()()"]For '((()))': prefix checks → ( [1≥0✓], (( [2≥0✓], ((( [3≥0✓], ((() [3≥1✓], ((()) [3≥2✓], ((())) [3≥3✓]. All prefixes valid, so the entire string is valid.
For ')(())(' (invalid): first character is ')' → prefix count of ( is 0, count of ) is 1 → 0≥1 is FALSE. Invalid at the very first character.
The key insight is that validity is a PREFIX property. We don't need to see the whole string to reject it—if any prefix violates the invariant (more close than open), the string is invalid regardless of what follows. This is perfect for backtracking: we can prune invalid branches as soon as they violate the invariant.
Let's understand why backtracking is the natural approach and how to frame the problem in terms of our template.
The Decision Tree:
At each position in the string we're building, we have two choices:
()But not all choices are valid at all times:
( only if we haven't used all n opens yet) only if it wouldn't unbalance the string (open_count > close_count)Building the Tree for n=2:
""
/ \
"(" ")"
/ \ X (invalid: 0 < 1)
"((" "()"
/ \ / \
"(()" "(()" "()(" "())"
\ X \ X (invalid: 1 < 2)
"(())" invalid "()()"
✓ ✓
The tree prunes invalid branches immediately, never wasting time exploring strings that start with ) or have more closes than opens at any point.
( and ).(: open_count < n (haven't used all opens yet).): close_count < open_count (wouldn't unbalance).We track 'open_count' and 'close_count' explicitly rather than computing them from the string. This O(1) tracking avoids O(n) string scanning at each step. Alternatively, track 'remaining open' and 'remaining close' and decrement—same idea, different bookkeeping.
Let's implement the solution with clear, documented code.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
def generateParenthesis(n: int) -> list[str]: """ Generate all valid combinations of n pairs of parentheses. Approach: Backtracking with balance constraints. - Add '(' if we have remaining opens (open_count < n) - Add ')' if it wouldn't unbalance (close_count < open_count) Time Complexity: O(4^n / sqrt(n)) — the n-th Catalan number Space Complexity: O(n) for recursion depth (excluding output) """ result = [] def backtrack(current: str, open_count: int, close_count: int) -> None: # Base case: used all n opens and n closes if len(current) == 2 * n: result.append(current) return # Choice 1: Add '(' if we have remaining opens if open_count < n: backtrack(current + '(', open_count + 1, close_count) # Choice 2: Add ')' if it wouldn't unbalance if close_count < open_count: backtrack(current + ')', open_count, close_count + 1) backtrack('', 0, 0) return result # Example usageprint(generateParenthesis(1)) # ['()']print(generateParenthesis(2)) # ['(())', '()()']print(generateParenthesis(3)) # ['((()))', '(()())', '(())()', '()(())', '()()()'] def generateParenthesis_optimized(n: int) -> list[str]: """ Optimized version using a list for O(1) append/pop instead of string concatenation (which creates new strings in Python). """ result = [] def backtrack(path: list[str], open_count: int, close_count: int) -> None: if len(path) == 2 * n: result.append(''.join(path)) return if open_count < n: path.append('(') backtrack(path, open_count + 1, close_count) path.pop() # Explicit unchoose if close_count < open_count: path.append(')') backtrack(path, open_count, close_count + 1) path.pop() # Explicit unchoose backtrack([], 0, 0) return resultIn Python and Java, strings are immutable, so 'current + "("' creates a new string. This means we don't need explicit 'unchoose'—the original string is unchanged. However, this has O(n) cost per operation. The optimized versions use mutable lists/StringBuilder for O(1) append/pop with explicit backtracking.
Let's thoroughly understand why our two simple conditions (open_count < n and close_count < open_count) fully capture validity.
Condition 1: open_count < n
This is straightforward: we can only add an open paren if we haven't used all n of them yet. If open_count == n, no more ( can be added.
Condition 2: close_count < open_count
This is the balance constraint. Let's prove it's correct:
Claim: A string is balanced at every prefix if and only if, at every step, close_count ≤ open_count.
Proof:
( so far) so far( count ≥ ) count) when close_count < open_countWhy These Two Conditions Are Sufficient:
If we only add characters when allowed by these conditions, and we stop when we've used exactly n of each, we guarantee:
These are exactly the two requirements for validity.
Alternative Framing: Remaining Counts
Some prefer tracking "remaining" instead of "used":
def generateParenthesis_alt(n: int) -> list[str]:
result = []
def backtrack(current: str, remaining_open: int, remaining_close: int) -> None:
if remaining_open == 0 and remaining_close == 0:
result.append(current)
return
# Can add '(' if we have remaining opens
if remaining_open > 0:
backtrack(current + '(', remaining_open - 1, remaining_close)
# Can add ')' if more opens have been used than closes
# (equivalently: remaining_close > remaining_open)
if remaining_close > remaining_open:
backtrack(current + ')', remaining_open, remaining_close - 1)
backtrack('', n, n)
return result
Both formulations are correct; choose whichever feels more intuitive.
Notice the elegant symmetry: open parentheses are limited by total count (n), while close parentheses are limited by balance (must be ≤ opens used). This asymmetry reflects the fundamental asymmetry of parentheses: opens can appear freely (up to n), but closes must wait for matching opens.
The complexity analysis of Generate Parentheses leads us to the fascinating Catalan numbers.
How Many Valid Strings?
The number of valid strings for n pairs is the n-th Catalan number:
C_n = (2n)! / ((n+1)! × n!)
First few values: C_1=1, C_2=2, C_3=5, C_4=14, C_5=42, C_6=132
Asymptotically: C_n ≈ 4^n / (n^(3/2) × √π)
Time Complexity:
We generate exactly C_n valid strings, each of length 2n:
Note: This is much better than 2^(2n) = 4^n because pruning eliminates invalid branches early.
Space Complexity:
| n | Catalan Number C_n | Valid Parentheses Strings |
|---|---|---|
| 1 | 1 | () |
| 2 | 2 | (()) , ()() |
| 3 | 5 | ((())), (()()), (())(), ()(()), ()()() |
| 4 | 14 | 14 strings |
| 5 | 42 | 42 strings |
| 6 | 132 | 132 strings |
| 10 | 16,796 | 16,796 strings |
| 15 | 9,694,845 | ~9.7 million strings |
Catalan numbers count many combinatorial structures: binary trees with n+1 leaves, ways to triangulate a convex polygon with n+2 sides, paths from (0,0) to (n,n) that don't cross the diagonal, ways to parenthesize n+1 factors, and more. Seeing C_n in parentheses generation connects you to a rich mathematical universe.
While backtracking is the most natural approach, there are other ways to solve this problem.
Approach 2: Closure-Based Recursion
Think of each valid sequence as the concatenation of:
( + (valid sequence A) + ) + (valid sequence B)Where A uses i pairs and B uses n-1-i pairs.
This leads to a closure-based recursive solution:
def generateParenthesis_closure(n: int) -> list[str]:
if n == 0:
return ['']
result = []
for i in range(n):
for left in generateParenthesis_closure(i):
for right in generateParenthesis_closure(n - 1 - i):
result.append('(' + left + ')' + right)
return result
This approach is elegant but has issues:
Approach 3: BFS with Queue
Instead of DFS (recursion), use BFS with a queue:
from collections import deque
def generateParenthesis_bfs(n: int) -> list[str]:
result = []
queue = deque([('', 0, 0)]) # (current, open_count, close_count)
while queue:
current, open_count, close_count = queue.popleft()
if len(current) == 2 * n:
result.append(current)
continue
if open_count < n:
queue.append((current + '(', open_count + 1, close_count))
if close_count < open_count:
queue.append((current + ')', open_count, close_count + 1))
return result
BFS produces strings in different order (shortest first, but all are same length here), essentially equivalent to backtracking for this problem.
The parentheses generation pattern extends to many related problems.
Variation 1: Multiple Types of Brackets
Generate valid combinations with multiple bracket types: (), [], {}.
def generateBrackets(n: int) -> list[str]:
"""Generate valid combinations of n pairs each of (), [], {}."""
result = []
brackets = ['()', '[]', '{}']
def backtrack(current, open_stack):
if len(current) == 6 * n: # 3 types × 2n characters each
result.append(current)
return
# Try adding an open bracket
for open_b, close_b in brackets:
# Track how many of each type used...
# More complex state management needed
# Try adding a close bracket (must match top of stack)
if open_stack and ...:
...
# Implementation more complex but same principle
Variation 2: Parentheses with Minimum Nesting
Generate only combinations with nesting depth ≥ k.
Variation 3: Letter Case Permutation
Not exactly parentheses, but similar backtracking structure—generate all casing variants of a string like "a1b2".
Variation 4: Valid IP Addresses
Restore IP addresses (LeetCode 93)—partition a string into 4 valid IP octets. Same backtracking pattern with different validity rules.
The Generate Parentheses pattern appears whenever you need to build strings/structures with balance constraints. The key insight is tracking 'open debt'—how many opens are waiting for matching closes. Any problem with nested/balanced structures likely uses this pattern.
Generate Parentheses is a classic interview question. Here's how to approach it systematically.
Step 1: Clarify (30 seconds)
Step 2: Approach (1 minute)
Step 3: Walk through example
Step 4: Code (5-7 minutes)
Step 5: Complexity (1 minute)
Mentioning Catalan numbers shows mathematical maturity. If asked 'how many results for n=10?', saying '16,796—that's the 10th Catalan number' impresses interviewers. But don't over-explain unless asked; it can seem like showing off.
Generate Parentheses is a quintessential backtracking problem that introduces balance constraints—a powerful concept that recurs throughout algorithm design.
Module Complete:
Congratulations! You've now mastered the four fundamental backtracking patterns in this module:
These patterns form the core of backtracking problem-solving. Most backtracking problems you encounter will be variations or combinations of these fundamental patterns.
You've completed Common Backtracking Patterns! You now have a comprehensive library of backtracking solutions covering grid exploration, numeric combinations, string partitioning, and balanced structure generation. These patterns will serve you in interviews, competitive programming, and real-world problem-solving. Continue to the next module to explore backtracking time complexity analysis in depth.