Skip to content

265. Generate Parentheses

๐Ÿ”— LeetCode Problem: 22. Generate Parentheses
๐Ÿ“Š Difficulty: Medium
๐Ÿท๏ธ Topics: Stack, Backtracking, String, Dynamic Programming

Problem Statement

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

Example 1:

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

Example 2:

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

Example 3:

Input: n = 2
Output: ["(())","()()"]

Constraints: - 1 <= n <= 8


๐ŸŒŸ ELI5: The Simple Idea!

Think of building valid parentheses step by step:

n = 2 means we have 2 opening '(' and 2 closing ')'

Valid combinations:
  "(())"  โœ“
  "()()"  โœ“

Invalid combinations:
  ")()("  โœ— - Starts with closing
  "((("   โœ— - Not all closed
  "((()"  โœ— - Too many opening

Rules to stay valid:
1. Can't use more '(' than we have (max n)
2. Can't use more ')' than we have (max n)
3. Can't close more than we've opened

The Building Process:

Start: ""
  Can add '(' (have 2 left)
  โ†’ "("

From "(":
  Can add '(' (have 1 left)
  โ†’ "(("
  Can add ')' (opened 1, can close)
  โ†’ "()"

Continue until all valid combinations found!


๐ŸŽจ Visual Understanding

Decision Tree for n=2

                        ""
                        โ†“
                       "("
                    โ†™       โ†˜
                 "(("         "()"
                  โ†“         โ†™    โ†˜
               "(()"     "()("   "()" โœ— (can't add more)
                  โ†“         โ†“
              "(())"    "()()"
                โœ“          โœ“

Valid paths lead to complete strings with 2n characters!

Building Process

n = 3 (3 pairs)

Goal: Use exactly 3 '(' and 3 ')'

Start: ""
  open = 0, close = 0

Step 1: Add '('
  "("
  open = 1, close = 0

Step 2: Can add '(' or ')'
  Branch 1: "((" (open=2, close=0)
  Branch 2: "()" (open=1, close=1)

Step 3: Continue branching...
  Eventually get all valid combinations:
  "((()))", "(()())", "(())()", "()(())", "()()()"

๐ŸŽฏ Approach 1: Backtracking (Recursion) โญโญ

The Most Common Solution

Algorithm:

Use backtracking with tracking:
  - How many '(' used (open)
  - How many ')' used (close)
  - Current string being built

Rules:
  1. Can add '(' if open < n
  2. Can add ')' if close < open
  3. If open == close == n โ†’ valid combination!

Implementation

import java.util.*;

/**
 * Backtracking approach
 * Time: O(4^n / โˆšn) - Catalan number
 * Space: O(n) - Recursion depth
 */
class Solution {
    private List<String> result;

    public List<String> generateParenthesis(int n) {
        result = new ArrayList<>();
        backtrack("", 0, 0, n);
        return result;
    }

    private void backtrack(String current, int open, int close, int max) {
        // Base case: completed valid combination
        if (current.length() == max * 2) {
            result.add(current);
            return;
        }

        // Add opening parenthesis
        if (open < max) {
            backtrack(current + "(", open + 1, close, max);
        }

        // Add closing parenthesis
        if (close < open) {
            backtrack(current + ")", open, close + 1, max);
        }
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.generateParenthesis(3));
        System.out.println(sol.generateParenthesis(1));
    }
}

โฐ Time: O(4^n / โˆšn) - Catalan number C_n
๐Ÿ’พ Space: O(n) - Maximum recursion depth

Using StringBuilder (More Efficient)

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 sb, 
                      int open, int close, int max) {
    if (sb.length() == max * 2) {
        result.add(sb.toString());
        return;
    }

    if (open < max) {
        sb.append('(');
        backtrack(result, sb, open + 1, close, max);
        sb.deleteCharAt(sb.length() - 1);  // Backtrack
    }

    if (close < open) {
        sb.append(')');
        backtrack(result, sb, open, close + 1, max);
        sb.deleteCharAt(sb.length() - 1);  // Backtrack
    }
}

More efficient - reuses StringBuilder!

๐Ÿ” Super Detailed Dry Run

Example: n = 2

Goal: Generate all valid combinations of 2 pairs

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
Backtrack Call Tree
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Call 1: backtrack("", open=0, close=0, max=2)
  Length: 0 < 4 โ†’ Continue

  Can add '('? open(0) < max(2) โ†’ Yes
    โ†’ Recurse: backtrack("(", 1, 0, 2)

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Call 2: backtrack("(", open=1, close=0, max=2)
  Length: 1 < 4 โ†’ Continue

  Can add '('? open(1) < max(2) โ†’ Yes
    โ†’ Recurse: backtrack("((", 2, 0, 2)

  Can add ')'? close(0) < open(1) โ†’ Yes
    โ†’ Recurse: backtrack("()", 1, 1, 2)

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Call 3: backtrack("((", open=2, close=0, max=2)
  Length: 2 < 4 โ†’ Continue

  Can add '('? open(2) < max(2) โ†’ No

  Can add ')'? close(0) < open(2) โ†’ Yes
    โ†’ Recurse: backtrack("(()", 2, 1, 2)

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Call 4: backtrack("(()", open=2, close=1, max=2)
  Length: 3 < 4 โ†’ Continue

  Can add '('? open(2) < max(2) โ†’ No

  Can add ')'? close(1) < open(2) โ†’ Yes
    โ†’ Recurse: backtrack("(())", 2, 2, 2)

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Call 5: backtrack("(())", open=2, close=2, max=2)
  Length: 4 == 4 โ†’ Base case!

  Add "(())" to result
  Return

  Result: ["(())"]

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

[Back to Call 4]
  Finished exploring ')'
  Return

[Back to Call 3]
  Finished exploring ')'
  Return

[Back to Call 2]
  Finished exploring '(' path

  Now explore ')' path:

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Call 6: backtrack("()", open=1, close=1, max=2)
  Length: 2 < 4 โ†’ Continue

  Can add '('? open(1) < max(2) โ†’ Yes
    โ†’ Recurse: backtrack("()(", 2, 1, 2)

  Can add ')'? close(1) < open(1) โ†’ No

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Call 7: backtrack("()(", open=2, close=1, max=2)
  Length: 3 < 4 โ†’ Continue

  Can add '('? open(2) < max(2) โ†’ No

  Can add ')'? close(1) < open(2) โ†’ Yes
    โ†’ Recurse: backtrack("()()", 2, 2, 2)

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Call 8: backtrack("()()", open=2, close=2, max=2)
  Length: 4 == 4 โ†’ Base case!

  Add "()()" to result
  Return

  Result: ["(())", "()()"]

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

[Back to Call 7, 6, 2, 1]
  All paths explored

Final Result: ["(())", "()()"]

Visual Decision Tree:

                    ("", 0, 0)
                        |
                    ("(", 1, 0)
                    /          \
            ("((", 2, 0)    ("()", 1, 1)
                |               |
            ("(()", 2, 1)   ("()(", 2, 1)
                |               |
            "(())" โœ“        "()()" โœ“

๐ŸŽฏ Approach 2: Iterative (Using Stack) โญ

Alternative Solution

Algorithm:

Use a stack to store states: (current_string, open_count, close_count)
Process like BFS/DFS until all combinations found

Implementation

import java.util.*;

class Solution {
    // State class to track current combination
    static class State {
        String str;
        int open;
        int close;

        State(String str, int open, int close) {
            this.str = str;
            this.open = open;
            this.close = close;
        }
    }

    public List<String> generateParenthesis(int n) {
        List<String> result = new ArrayList<>();
        Stack<State> stack = new Stack<>();

        stack.push(new State("", 0, 0));

        while (!stack.isEmpty()) {
            State current = stack.pop();

            // If we've used all parentheses
            if (current.open == n && current.close == n) {
                result.add(current.str);
                continue;
            }

            // Try adding '('
            if (current.open < n) {
                stack.push(new State(
                    current.str + "(", 
                    current.open + 1, 
                    current.close
                ));
            }

            // Try adding ')'
            if (current.close < current.open) {
                stack.push(new State(
                    current.str + ")", 
                    current.open, 
                    current.close + 1
                ));
            }
        }

        return result;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.generateParenthesis(2));
    }
}

โฐ Time: O(4^n / โˆšn)
๐Ÿ’พ Space: O(4^n / โˆšn) - Stack can hold many states

๐Ÿ” Detailed Dry Run (Iterative)

n = 2
stack = [("", 0, 0)]

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Iteration 1:
  Pop: ("", 0, 0)
  Not complete (0 != 2)
  Push: ("(", 1, 0)

  Stack: [("(", 1, 0)]

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Iteration 2:
  Pop: ("(", 1, 0)
  Not complete
  Can add '('? 1 < 2 โ†’ Yes, push ("((", 2, 0)
  Can add ')'? 0 < 1 โ†’ Yes, push ("()", 1, 1)

  Stack: [("((", 2, 0), ("()", 1, 1)]

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Iteration 3:
  Pop: ("()", 1, 1)
  Not complete
  Can add '('? 1 < 2 โ†’ Yes, push ("()(", 2, 1)
  Can add ')'? 1 < 1 โ†’ No

  Stack: [("((", 2, 0), ("()(", 2, 1)]

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Iteration 4:
  Pop: ("()(", 2, 1)
  Not complete
  Can add '('? 2 < 2 โ†’ No
  Can add ')'? 1 < 2 โ†’ Yes, push ("()()", 2, 2)

  Stack: [("((", 2, 0), ("()()", 2, 2)]

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Iteration 5:
  Pop: ("()()", 2, 2)
  Complete! open=2, close=2
  Add "()()" to result โœ“

  Result: ["()()"]
  Stack: [("((", 2, 0)]

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Iteration 6:
  Pop: ("((", 2, 0)
  Not complete
  Can add '('? 2 < 2 โ†’ No
  Can add ')'? 0 < 2 โ†’ Yes, push ("(()", 2, 1)

  Stack: [("(()", 2, 1)]

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Iteration 7:
  Pop: ("(()", 2, 1)
  Not complete
  Can add '('? 2 < 2 โ†’ No
  Can add ')'? 1 < 2 โ†’ Yes, push ("(())", 2, 2)

  Stack: [("(())", 2, 2)]

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Iteration 8:
  Pop: ("(())", 2, 2)
  Complete! open=2, close=2
  Add "(())" to result โœ“

  Result: ["()()", "(())"]
  Stack: []

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Stack empty โ†’ Done!

Final Result: ["()()", "(())"]

โš ๏ธ Common Mistakes

Mistake 1: Wrong condition for adding ')'

// โŒ WRONG - Allows more ')' than '('
if (close < max) {
    backtrack(result, current + ")", open, close + 1, max);
}
// Generates: "))((" โœ—

// โœ“ CORRECT - Ensures ')' doesn't exceed '('
if (close < open) {
    backtrack(result, current + ")", open, close + 1, max);
}

Mistake 2: Wrong base case

// โŒ WRONG - Only checks one counter
if (open == max) {
    result.add(current);
    return;
}
// Generates incomplete: "(((" โœ—

// โœ“ CORRECT - Check both counters OR length
if (current.length() == max * 2) {
    result.add(current);
    return;
}
// OR
if (open == max && close == max) {
    result.add(current);
    return;
}

Mistake 3: Not checking open < n

// โŒ WRONG - No limit on '('
backtrack(result, current + "(", open + 1, close, max);

// โœ“ CORRECT - Check first
if (open < max) {
    backtrack(result, current + "(", open + 1, close, max);
}

Mistake 4: Forgetting to backtrack with StringBuilder

// โŒ WRONG - Doesn't undo!
sb.append('(');
backtrack(result, sb, open + 1, close, max);
// sb still has '(' for next iteration!

// โœ“ CORRECT - Undo after recursion
sb.append('(');
backtrack(result, sb, open + 1, close, max);
sb.deleteCharAt(sb.length() - 1);  // Backtrack!

Mistake 5: Using mutable list incorrectly

// โŒ WRONG - Modifying shared list
List<Character> current = new ArrayList<>();
// Pass same list to all recursive calls
// All branches modify same list!

// โœ“ CORRECT - Use String or copy list
String current = "";
// OR create new list for each branch


๐ŸŽฏ Pattern Recognition

Problem Type: Generate All Valid Combinations
Core Pattern: Backtracking + Stack Validation

When to Apply:
โœ“ Generate all valid sequences
โœ“ Build valid structures recursively
โœ“ Explore all possibilities with constraints
โœ“ Combination generation

Recognition Keywords:
- "generate all"
- "well-formed"
- "valid combinations"
- "parentheses"
- "n pairs"

Similar Problems:
- Valid Parentheses (LC 20) - Validation (easier)
- Different Ways to Add Parentheses (LC 241)
- Remove Invalid Parentheses (LC 301) - Hard
- Longest Valid Parentheses (LC 32) - Hard

Key Components:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Backtracking: Explore all valid paths     โ”‚
โ”‚ Rule 1: open < n (can add '(')            โ”‚
โ”‚ Rule 2: close < open (can add ')')        โ”‚
โ”‚ Base case: length == 2n                   โ”‚
โ”‚ Optimization: StringBuilder reuse         โ”‚
โ”‚ Time: O(4^n/โˆšn), Space: O(n)              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿง  Interview Strategy

Step 1: "This is a backtracking problem"
Step 2: "I need to generate all valid combinations"
Step 3: "I'll use two counters to track usage"

Key Points to Mention:
- Backtracking explores all valid paths
- Two rules: can add '(' if open < n
             can add ')' if close < open
- Base case: length equals 2n
- Can optimize with StringBuilder reuse
- Time: Catalan number O(4^n/โˆšn)

Walk Through Example:
"For n=2:
 Start with empty string
 Add '(' โ†’ '('
   Can add '(' โ†’ '((' โ†’ '(()'  โ†’ '(())' โœ“
   Can add ')' โ†’ '()' โ†’ '()(' โ†’ '()()' โœ“
 Both valid combinations found"

Why Backtracking:
"Need to explore all possibilities.
 At each step, try adding '(' or ')'.
 Rules prevent invalid combinations.
 Backtracking naturally tries all valid paths."

Complexity:
"Time is O(4^n/โˆšn) - Catalan number.
 This is optimal for generating all combinations.
 Space is O(n) for recursion depth.
 Can't improve asymptotically."

Edge Cases to Mention:
โœ“ n = 1 โ†’ "()"
โœ“ n = 2 โ†’ "(())", "()()"
โœ“ n = 3 โ†’ 5 combinations
โœ“ Order doesn't matter in result

๐Ÿ“ Quick Revision Notes

๐ŸŽฏ Core Concept:

Generate Parentheses: Use backtracking to build all valid combinations. Track open and close counts. Add '(' if open < n. Add ')' if close < open. Base case: length == 2n. Time: Catalan number O(4^n/โˆšn).

โšก Quick Implementation:

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class Solution {
  private static class State {
    String str;
    int open;
    int close;

    public State(String str, int open, int close) {
      this.str = str;
      this.open = open;
      this.close = close;
    }
  }

  public List<String> generateParenthesis(int n) {
    // return recursive(n);
    return iterative(n);
  }

  private List<String> iterative(int n) {
    List<String> res = new ArrayList<>();
    StringBuilder sb = new StringBuilder();

    // step 1: same as before except that we maintain state in stack
    Stack<State> stack = new Stack<>();
    stack.push(new State("", 0, 0));

    // step 2: loop till stack becomes empty
    while (!stack.isEmpty()) {
      State curr = stack.pop();

      String str = curr.str;
      int open = curr.open;
      int close = curr.close;

      // step 4: base case
      if (open == n && close == n) {
        res.add(curr.str);

        continue;
      }

      // step 3: same as recursive.
      // we should not use sb as its diff from recursion
      // as we keep on processing that sb in later calls.
      // Here its not like that.
      if (open < n) {
        stack.push(new State(str.concat("("), open + 1, close));
      }

      if (close < open) {
        stack.push(new State(str.concat(")"), open, close + 1));
      }
    }

    return res;
  }

  private List<String> recursive(int n) {
    List<String> res = new ArrayList<>();

    recursiveUtil(0, 0, new StringBuilder(), n, res);

    return res;
  }

  private void recursiveUtil(int open, int close, StringBuilder sb, int n, List<String> res) {
    // step 5: base case
    if (open == n && close == n) {
      res.add(sb.toString());
    }

    // step 1: check if we can add open paranthesis
    if (open < n) {
      // step 2: if we can add, add it and go to next possible
      sb.append("(");
      recursiveUtil(open + 1, close, sb, n, res);
      // step 3: remove the added paranthesis and check for next possible
      sb.deleteCharAt(sb.length() - 1);
    }

    // step 4: check if we can add close paranthesis
    if (close < open) {
      sb.append(")");
      recursiveUtil(open, close + 1, sb, n, res);
      sb.deleteCharAt(sb.length() - 1);
    }
  }

  public static void main(String[] args) {
    Solution s = new Solution();
    // .equals(List.of("((()))", "(()())", "(())()", "()(())", "()()()"))
    System.out
        .println(s.generateParenthesis(3));
  }
}

๐Ÿ”‘ Key Insights:

  • Pattern: Backtracking + Validation
  • Rule 1: Can add '(' if open < n
  • Rule 2: Can add ')' if close < open
  • Base case: length == 2n
  • Guarantees: Always valid, all combinations
  • Time: O(4^n/โˆšn), Space: O(n) โœ“

๐ŸŽช Memory Aid:

"Open before close, count both, explore all!"
"Add ( if can, add ) if have (, done at 2n!" โœจ

โš ๏ธ Two Critical Rules:

Rule 1: if (open < max)
  โ†’ Can still add opening bracket
  โ†’ Haven't used all n yet

Rule 2: if (close < open)
  โ†’ Can add closing bracket
  โ†’ Have unmatched opening to close

Without Rule 2:
  Could generate ")(" โœ—

Without Rule 1:
  Could generate "(((" โœ—

Both rules together โ†’ Always valid!

๐Ÿงช Edge Cases

Case 1: n = 1

Input: n = 1
Output: ["()"]
Only one valid combination

Case 2: n = 2

Input: n = 2
Output: ["(())", "()()"]
Two valid combinations

Case 3: n = 3

Input: n = 3
Output: ["((()))","(()())","(())()","()(())","()()()"]
Five valid combinations (Catalan number C_3 = 5)

All handled correctly! โœ“


๐ŸŽ“ Complexity Analysis

Time Complexity: O(4^n / โˆšn)

This is the n-th Catalan number!

Catalan number C_n = (2n)! / ((n+1)! * n!)

Asymptotic: O(4^n / (n^(3/2)))

Why?
  - Each valid combination has 2n characters
  - Number of valid combinations is C_n
  - Total characters generated: C_n ร— 2n

For small n:
  n=1: 1 combination
  n=2: 2 combinations
  n=3: 5 combinations
  n=4: 14 combinations
  n=5: 42 combinations

Space Complexity: O(n)

Recursion stack depth:
  Maximum depth = 2n (building string of length 2n)
  Each call: O(1) space (just variables)
  Total: O(n)

Result storage not counted in space complexity
(output doesn't count towards auxiliary space)

Same Core Pattern: - Letter Combinations of Phone Number (LC 17) - Backtracking - Permutations (LC 46) - Backtracking - Subsets (LC 78) - Backtracking

Parentheses Family: - Valid Parentheses (LC 20) - Validation (easier) - Minimum Add to Make Valid (LC 921) - Remove Invalid Parentheses (LC 301) - Hard - Longest Valid Parentheses (LC 32) - Hard


Happy practicing! ๐ŸŽฏ

Note: This is a MEDIUM backtracking problem! Master this and you understand: (1) backtracking fundamentals, (2) constraint-based generation, (3) Catalan numbers, (4) pruning invalid paths. This pattern extends to many combination generation problems. Very common at Amazon, Meta, Google! ๐Ÿ’ชโœจ