title: Generate Parentheses slug: generate-parentheses difficulty: medium leetcode_id: 22 leetcode_url: https://leetcode.com/problems/generate-parentheses/ categories: - strings - recursion patterns: - backtracking description: | Given `n` pairs of parentheses, write a function to *generate all combinations of well-formed parentheses*. A well-formed parentheses string has equal numbers of opening and closing parentheses, with every closing parenthesis matching a preceding opening one. constraints: | - `1 <= n <= 8` examples: - input: "n = 3" output: '["((()))","(()())","(())()","()(())","()()()"]' explanation: "All 5 valid combinations of 3 pairs of parentheses." - input: "n = 1" output: '["()"]' explanation: "With just one pair, there's only one valid combination." explanation: intuition: | Imagine you're building a string character by character, and at each step you can choose to add either an opening `(` or a closing `)` parenthesis. The key insight is that not every choice is valid. A string of parentheses is **well-formed** if at any point while reading left-to-right, the number of closing parentheses never exceeds the number of opening ones. Think of it like a balance: each `(` adds +1 to the balance, and each `)` subtracts 1. The balance must never go negative. This naturally leads to a **decision tree** approach. At each position, we branch based on what characters we *can* legally add: - We can add `(` if we haven't used all `n` opening parentheses yet - We can add `)` if we have more opening parentheses than closing ones (i.e., there's an unmatched `(` to close) By exploring all valid paths through this decision tree, we generate exactly the set of well-formed parentheses strings — no more, no less. approach: | We solve this using **Backtracking** — systematically building candidates and abandoning paths that can't lead to valid solutions. **Step 1: Define the recursive state** - `current`: The string we're building - `open_count`: Number of `(` parentheses used so far - `close_count`: Number of `)` parentheses used so far   **Step 2: Identify base case** - When `len(current) == 2 * n`, we've placed all parentheses - Add the completed string to our results list   **Step 3: Define recursive choices** - **Add `(`**: Only if `open_count < n` (we haven't used all opening parentheses) - **Add `)`**: Only if `close_count < open_count` (there's an unmatched `(` to close)   **Step 4: Backtrack after each choice** - After exploring a path, the recursion naturally "unwinds" - Since we pass strings (immutable in Python), backtracking is implicit - With mutable structures, you'd explicitly remove the last character   The constraints on when we can add each character ensure we only generate valid combinations, making this more efficient than generating all permutations and filtering. common_pitfalls: - title: Generating All Permutations Then Filtering description: | A naive approach might generate all possible strings of `(` and `)` characters, then filter for valid ones. With `n = 8`, that's `2^16 = 65,536` strings to generate and validate, but only `1,430` are valid (the 8th Catalan number). This wastes significant computation on invalid strings. The backtracking approach only explores valid paths, never generating invalid strings in the first place. wrong_approach: "Generate all 2^(2n) strings, filter valid ones" correct_approach: "Use constraints during generation to only build valid strings" - title: Forgetting the Close Constraint description: | It's tempting to think you can always add a `)` as long as you haven't used all `n` of them. But consider building with `n = 2`: Starting with `()`, if you add `)` next you get `())` — this is invalid because the third character closes a parenthesis that was never opened. The rule is: you can only add `)` when `close_count < open_count`, not just when `close_count < n`. wrong_approach: "Add ) whenever close_count < n" correct_approach: "Add ) only when close_count < open_count" - title: Modifying Strings In-Place Incorrectly description: | In languages with mutable strings or when using a list to build the string, forgetting to backtrack (remove the last character after recursion) leads to corrupted results. In Python, passing `current + '('` creates a new string, so backtracking is automatic. But if using a list like `current.append('(')`, you must call `current.pop()` after the recursive call returns. key_takeaways: - "**Backtracking pattern**: Build solutions incrementally, using constraints to prune invalid paths early" - "**Decision tree thinking**: Visualise recursive problems as trees where each node is a choice point" - "**Catalan numbers**: The count of valid parentheses combinations follows the Catalan sequence — this appears in many combinatorial problems" - "**Constraint propagation**: Encoding validity rules into the recursion conditions is more efficient than post-hoc filtering" time_complexity: "O(4^n / √n). This is the nth Catalan number, representing the count of valid combinations. Each valid string takes O(n) to construct." space_complexity: "O(n). The recursion depth is at most `2n` (the length of each string), and we store the current string being built." solutions: - approach_name: Backtracking is_optimal: true code: | def generate_parenthesis(n: int) -> list[str]: result = [] def backtrack(current: str, open_count: int, close_count: int): # Base case: we've placed all 2n parentheses if len(current) == 2 * n: result.append(current) return # Choice 1: Add opening parenthesis if we haven't used all n if open_count < n: backtrack(current + '(', open_count + 1, close_count) # Choice 2: Add closing parenthesis if it won't make string invalid if close_count < open_count: backtrack(current + ')', open_count, close_count + 1) backtrack('', 0, 0) return result explanation: | **Time Complexity:** O(4^n / √n) — The number of valid sequences is the nth Catalan number. **Space Complexity:** O(n) — Recursion stack depth plus the current string being built. We recursively build strings by making valid choices at each step. The constraints (`open_count < n` and `close_count < open_count`) ensure we never explore invalid paths, making this efficient despite the exponential output size. - approach_name: Iterative with Stack is_optimal: false code: | def generate_parenthesis(n: int) -> list[str]: result = [] # Stack holds tuples of (current_string, open_count, close_count) stack = [('', 0, 0)] while stack: current, open_count, close_count = stack.pop() # Base case: complete string if len(current) == 2 * n: result.append(current) continue # Add closing parenthesis option first (will be processed second due to LIFO) if close_count < open_count: stack.append((current + ')', open_count, close_count + 1)) # Add opening parenthesis option if open_count < n: stack.append((current + '(', open_count + 1, close_count)) return result explanation: | **Time Complexity:** O(4^n / √n) — Same as recursive, we explore all valid paths. **Space Complexity:** O(4^n / √n) — The stack can hold many partial solutions simultaneously. This converts the recursion to an explicit stack, which can be useful in languages with limited recursion depth. The logic is identical — we just manage the call stack manually. Note that space complexity is worse because we store all pending states on the heap rather than using the call stack. - approach_name: Dynamic Programming is_optimal: false code: | def generate_parenthesis(n: int) -> list[str]: # dp[i] contains all valid strings with i pairs of parentheses dp = [[] for _ in range(n + 1)] dp[0] = [''] # Base case: empty string for 0 pairs for i in range(1, n + 1): # Build strings for i pairs using smaller subproblems # Pattern: "(" + dp[j] + ")" + dp[i-1-j] for all valid j for j in range(i): for left in dp[j]: for right in dp[i - 1 - j]: dp[i].append('(' + left + ')' + right) return dp[n] explanation: | **Time Complexity:** O(4^n / √n) — We generate all Catalan(n) strings. **Space Complexity:** O(4^n / √n) — We store all valid strings for all values up to n. This builds solutions bottom-up. For `i` pairs, we consider all ways to split: `j` pairs inside the first `()` and `i-1-j` pairs after it. While correct, this uses more memory than backtracking since it stores all intermediate results.