Given n pairs, list every well-formed combination — grow one valid prefix at a time, opening while opens remain and closing only while closes trail opens.
Open while you still have opens to spend, close only while closes trail opens — every answer is one root-to-leaf path through those two gates.
A partial string is buildable into a valid answer exactly when it never dips negative — closes never exceed opens, and opens never exceed n. So the whole search state is just the pair (opens used, closes used), and each step faces at most two legal moves. The recursion walks every legal prefix depth-first; leaves at length 2n are precisely the answers.
Step through n = 3. Each frame appends one bracket through a gate or undoes it; the five lime frames are the moments the path reaches length 6 and a copy is recorded. Try n = 2 to see nesting versus chaining side by side.
class Solution:
def generateParenthesis(self, n: int):
res = []
def dfs(path, open, close):
if len(path) == 2 * n: # both counters spent: one full answer
res.append(''.join(path)) # join now: path keeps mutating
return
if open < n: # gate 1: opens remain to spend
path.append('(')
dfs(path, open + 1, close)
path.pop() # undo: free the slot for the sibling
if close < open: # gate 2: a matching open exists
path.append(')')
dfs(path, open, close + 1)
path.pop() # undo: restore state for the caller
dfs([], 0, 0)
return res
The displayed Python and the trace above use the same logic: full-length copy, the two gate checks, and a paired pop after each recursive call.
if len(path) == 2 * nThe only place answers are recorded. Both counters are exhausted here, so the path holds n opens and n closes in a valid order — exactly one answer per leaf.if open < nAn ( is legal only while opens remain. Once open reaches n, that entire subtree is closed — every continuation from here is closes only.if close < openA ) is legal only while an unmatched ( exists. This single check is what keeps every prefix non-negative and prunes all invalid strings before they are built.((())), (()()), (())(), ()(()), ()()() — the first is pure nesting, the last pure chaining, and the middle three mix. The trace's five lime frames arrive in exactly this order.
n = 2 already shows both shapes: nest the second pair inside or chain it after. Every larger n just interleaves these two moves under the same gates.
I track opens and closes used and grow one prefix depth-first: I append an open parenthesis while opens remain, a close parenthesis only while closes trail opens, and I record a copy when the path reaches length two n. The gates admit exactly the valid prefixes, so each complete string is generated once and nothing invalid is ever built.