Skip to content

Generating Valid Parentheses Combinations in Python

Updated: at 03:12 AM

Parentheses are a fundamental concept in computer programming and mathematics to control order of operations and group expressions. Generating all possible valid combinations of parentheses is a common technical interview coding challenge. This task tests a developer’s understanding of recursion, stacks, tree traversal, and dynamic programming.

In this comprehensive guide, we will examine different methods to generate valid parentheses combinations in Python.

Table of Contents

Open Table of Contents

Overview

The problem statement is:

Given an integer n, generate all possible valid combinations of n pairs of parentheses.

For example, if n = 3, the valid combinations are:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

Some key points:

This type of combinatorial generation problem lends itself well to recursion and dynamic programming solutions. We will examine 5 different methods:

  1. Recursive Backtracking
  2. Iterative Using Stack
  3. Recursive with Memoization
  4. Iterative Dynamic Programming
  5. BFS Tree Traversal

For each approach, we will break down the key steps with example code and explanations. Pros and cons of each method will also be analyzed.

Method 1: Recursive Backtracking

Recursive backtracking is a straightforward technique for this problem. We start with an empty string, then recursively add either a left or right parenthesis at each step. Backtracking allows exploring all possible combinations.

Python Implementation

def generate_parens(n):
    combinations = []
    generate(combinations, "", 0, 0, n)
    return combinations

def generate(combinations, current, openN, closeN, max):
    if len(current) == max*2:
        combinations.append(current)
        return

    if openN < max:
        generate(combinations, current+"(", openN+1, closeN, max)

    if closeN < openN:
        generate(combinations, current+")", openN, closeN+1, max)

How it Works

Analysis

Method 2: Iterative Using Stack

We can optimize the backtracking approach by using a stack to iteratively build the combinations.

Python Implementation

def generate_parens(n):
    combinations = []
    stack = [('(', 1, 0)]

    while stack:
        current, openN, closeN = stack.pop()

        if len(current) == n*2:
            combinations.append(current)
        else:
            if openN < n:
                stack.append((current+'(', openN+1, closeN))

            if closeN < openN:
                stack.append((current+')', openN, closeN+1))

    return combinations

How it Works

Analysis

Method 3: Recursive with Memoization

We can optimize the recursive backtracking solution using memoization - storing results of solved subproblems to avoid recomputing them.

Python Implementation

from functools import lru_cache

@lru_cache(maxsize=None)
def generate_parens(n):
    if n == 0:
        return ['']
    combinations = []

    for c in generate_parens(n-1):
        combinations.append('('+c+')')
        combinations.append('()'+c)
        combinations.append(c+'()')

    return combinations

How it Works

Analysis

Method 4: Iterative Dynamic Programming

We can further optimize using iterative bottom-up dynamic programming.

Python Implementation

def generate_parens(n):
    combinations = [['']]

    for i in range(1,n+1):
        temp = []

        for c in combinations[i-1]:
            temp.append('('+c+')')
            temp.append('()'+c)
            temp.append(c+'()')

        combinations.append(temp)

    return combinations[-1]

How it Works

Analysis

Method 5: BFS Tree Traversal

We can model this problem as traversing a tree breadth-first and collect valid leaf nodes.

Python Implementation

from collections import deque

def generate_parens(n):
    queue = deque([('(', 1, 0)])
    combinations = []

    while queue:
        current, openN, closeN = queue.popleft()

        if len(current) == n*2:
            combinations.append(current)
        else:
            if openN < n:
                queue.append((current+'(', openN+1, closeN))

            if closeN < openN:
                queue.append((current+')', openN, closeN+1))

    return combinations

How it Works

Analysis

Summary and Recommendations

For interview coding questions, I recommend either the iterative stack-based or dynamic programming solutions, which offer the best time/space complexity. Make sure to explain the algorithm and analysis to the interviewer.

Some follow-up opportunities:

In summary, generating valid parentheses combinations evaluates fundamental coding concepts like recursion, trees, dynamic programming, and backtracking search. Mastering this question demonstrates strong technical and analytical skills for programming interviews.