Generate Parentheses
Question
Given an integer , generate all possible combinations of well-formed parentheses of length . A sequence is "well-formed" if every opening parenthesis ( is closed in the correct order, and no closing parenthesis ) ever appears without a matching preceding (.
Solution
Pattern
Backtracking (State-Space Search) We incrementally build the string character by character, only exploring paths that maintain the "well-formed" invariant, and undoing our last move to explore other branches.
How to Identify
- Generate All Combinations: Whenever a problem asks to find all possible valid arrangements of a set.
- Small Constraints: is typically small (e.g., ), suggesting that an exponential time complexity is expected.
- Prunable Search Space: The validity of a partial solution can be checked at every step (you can't add a
)if it makes the string unbalanced). - Matching/Nesting: Problems involving balanced brackets, HTML tags, or nested levels.
Description
We use a recursive helper function to track the state using two counters: openCount (total ( placed) and closeCount (total ) placed).
- Base Case: If the current string length reaches , we have a complete valid combination. Add it to the result list.
- Decision 1 (Place Open): If
openCount < n, we can always choose to add a(. - Decision 2 (Place Close): If
closeCount < openCount, we can choose to add a). This condition is the core invariant that ensures the string remains well-formed. - Backtracking: To optimize for memory, use a single
StringBuilder. Append a character, recurse, then remove that character (backtrack) before moving to the next decision branch.
The Intuition
Think of this as a Decision Tree. At each step, you have two choices: add ( or ). However, the tree is "pruned" to prevent invalid states.
- You cannot add more than open brackets.
- You cannot add a closing bracket if it would exceed the number of currently open brackets.
Complexity
| Label | Worst | Average |
|---|---|---|
| Time Complexity | ||
| Space Complexity |
Time Complexity
The number of valid combinations is the Catalan Number, . Asymptotically, this is . Since each valid string takes to build and copy, the total work is .
Space Complexity
The maximum depth of the recursion stack is , yielding auxiliary space. The StringBuilder also maintains a length of . (Note: We exclude the space of the output list as per standard interview auxiliary space analysis).
Code
class Solution {
public List<String> generateParenthesis(int n) {
List<String> result = new ArrayList<>();
// Use StringBuilder for O(1) mutations instead of O(N) string copies
backtrack(result, new StringBuilder(), 0, 0, n);
return result;
}
private void backtrack(List<String> res, StringBuilder sb, int open, int close, int n) {
// Base case: String is full length
if (sb.length() == 2 * n) {
res.add(sb.toString());
return;
}
// Choice 1: Add an opening bracket if we haven't reached the limit n
if (open < n) {
sb.append("(");
backtrack(res, sb, open + 1, close, n);
sb.deleteCharAt(sb.length() - 1); // Backtrack step
}
// Choice 2: Add a closing bracket only if it maintains balance (close < open)
if (close < open) {
sb.append(")");
backtrack(res, sb, open, close + 1, n);
sb.deleteCharAt(sb.length() - 1); // Backtrack step
}
}
}Concepts to Think About
- Catalan Number Formula: . This appears in many counting problems like Binary Search Tree structures and Polygon Triangulations.
- String Immutability: In Java,
Stringis immutable. Usings + "("inside recursion creates copies, leading to overhead per valid path. Always preferStringBuilder. - DFS vs. BFS: Backtracking is a Depth-First Search. A Breadth-First Search would require significantly more memory to store all partial combinations at each level.
- Pruning: This algorithm is efficient because it never even considers an invalid string like
()). - Tail Recursion: Note that Java does not optimize for tail recursion; hence, the stack depth is a real constraint for very large .
- DP Approach: You can solve this by building up from smaller where .
Logical Follow-up
Question: How would you check if a single given string is valid in extra space?
Solution: Iterate through the string while maintaining a balance counter. Increment for (, decrement for ). If balance ever drops below 0, or is not 0 at the end, the string is invalid.
Question: (Google L5 Role) If is too large to store the results in a list (e.g. ), how can you find the "Lexicographically Next" valid combination?
Solution: To find the next string: (1) Find the rightmost () and swap it to )(. (2) Fill the remaining suffix with the smallest valid sequence (all remaining ( followed by all remaining )). This allows iterating through combinations one-by-one with space.
Question: How would you generate parentheses in a distributed system (Sharding)?
Solution: Shard by prefix. Machine A generates all valid strings starting with ((, Machine B generates strings starting with (), etc. Use a "prefix-aware" backtracking function to start at a specific state.