Parentheses and Bracket Patterns

1. Valid Parentheses Using Stack

Approach Method Time Space
Stack Matching Push opening brackets, pop and match closing O(n) O(n)
Counter Optimization For single type, use counter instead of stack O(n) O(1)
HashMap Lookup Map closing to opening for quick validation O(n) Cleaner code
Replace Pattern Repeatedly remove valid pairs (inefficient) O(n^2) Not recommended

Example: Stack-based validation

function isValid(s) {
    const stack = [];
    const pairs = {
        ')': '(',
        '}': '{',
        ']': '['
    };
    
    for (const char of s) {
        if (char === '(' || char === '{' || char === '[') {
            // Opening bracket - push to stack
            stack.push(char);
        } else {
            // Closing bracket - check match
            if (stack.length === 0) {
                return false; // No opening bracket
            }
            
            const top = stack.pop();
            if (top !== pairs[char]) {
                return false; // Mismatch
            }
        }
    }
    
    // Valid if stack is empty (all matched)
    return stack.length === 0;
}

// Example: "({})"
// '(' -> push
// '{' -> push
// '}' -> pop '{', match ✓
// ')' -> pop '(', match ✓
// Stack empty -> valid

// Example: "([)]"
// '(' -> push
// '[' -> push
// ')' -> pop '[', mismatch ✗

Example: Cleaner with early returns

function isValidCleaner(s) {
    const stack = [];
    const map = new Map([
        [')', '('],
        ['}', '{'],
        [']', '[']
    ]);
    
    for (const char of s) {
        if (map.has(char)) {
            // Closing bracket
            const top = stack.pop();
            if (top !== map.get(char)) {
                return false;
            }
        } else {
            // Opening bracket
            stack.push(char);
        }
    }
    
    return stack.length === 0;
}

// Key insights:
// 1. Stack stores opening brackets only
// 2. Closing bracket triggers pop and validation
// 3. Empty stack at end means all matched
// 4. Early return on mismatch or empty stack

Example: Optimized for single bracket type

// For strings with only one type of bracket: ()
function isValidSingleType(s) {
    let count = 0;
    
    for (const char of s) {
        if (char === '(') {
            count++;
        } else if (char === ')') {
            count--;
            if (count < 0) {
                return false; // More closing than opening
            }
        }
    }
    
    return count === 0;
}

// O(1) space vs O(n) for stack
// Can't handle multiple bracket types
// Useful for specific problems like balanced parentheses count

2. Generate Parentheses (Backtracking)

Approach Strategy Time Space
Backtracking Add '(' or ')' based on constraints O(4^n / √n) Catalan O(n) depth
Constraint: Open Can add '(' if open < n Ensures not too many Critical rule
Constraint: Close Can add ')' if close < open Ensures valid matching Critical rule
Closure Number DP approach using Catalan recurrence O(4^n / √n) Alternative method

Example: Backtracking generation

function generateParenthesis(n) {
    const result = [];
    
    function backtrack(current, open, close) {
        // Base case: generated n pairs
        if (current.length === 2 * n) {
            result.push(current);
            return;
        }
        
        // Can add opening bracket if haven't used n yet
        if (open < n) {
            backtrack(current + '(', open + 1, close);
        }
        
        // Can add closing bracket if it matches an opening
        if (close < open) {
            backtrack(current + ')', open, close + 1);
        }
    }
    
    backtrack('', 0, 0);
    return result;
}

// Example: n=3
// Result: ["((()))","(()())","(())()","()(())","()()()"]

// Decision tree for n=2:
//                    ""
//              (0,0)
//               /
//             "("
//           (1,0)
//          /     \
//       "(("    "()"
//      (2,0)   (1,1)
//        |       |
//     "(()"    "()(
//     (2,1)    (2,1)
//       |        |
//    "(())"   "()()"
//    (2,2)    (2,2)

Example: Alternative with explicit tracking

function generateParenthesisExplicit(n) {
    const result = [];
    
    function generate(current, open, close, max) {
        if (current.length === max * 2) {
            result.push(current);
            return;
        }
        
        // Always try adding '(' first (if possible)
        if (open < max) {
            generate(current + '(', open + 1, close, max);
        }
        
        // Then try adding ')' (if valid)
        if (close < open) {
            generate(current + ')', open, close + 1, max);
        }
    }
    
    generate('', 0, 0, n);
    return result;
}

// Key constraints visualized:
// - open < n: haven't exhausted opening brackets
// - close < open: have unmatched opening brackets
// Both must be true to maintain validity
Note: The number of valid parentheses combinations for n pairs is the nth Catalan number: $C_n = \frac{1}{n+1}\binom{2n}{n} = \frac{(2n)!}{(n+1)!n!}$. For n=3, this is 5 combinations. The time complexity is proportional to the number of valid combinations.

3. Longest Valid Parentheses (DP Solution)

Approach Method Time Space
Dynamic Programming dp[i] = length ending at index i O(n) O(n)
Stack Indices Track indices of unmatched brackets O(n) O(n)
Two Pass Counter Left-to-right then right-to-left O(n) O(1)
Brute Force Check all substrings O(n^3) Not practical

Example: DP approach

function longestValidParentheses(s) {
    const n = s.length;
    const dp = new Array(n).fill(0);
    let maxLen = 0;
    
    for (let i = 1; i < n; i++) {
        if (s[i] === ')') {
            if (s[i - 1] === '(') {
                // Case 1: ...()
                dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
            } else if (i - dp[i - 1] > 0 && 
                       s[i - dp[i - 1] - 1] === '(') {
                // Case 2: ...))
                // dp[i-1] is length of valid substring ending at i-1
                // Check if char before that substring is '('
                dp[i] = dp[i - 1] + 2;
                
                // Add length before matching '(' if exists
                const beforeMatch = i - dp[i - 1] - 2;
                if (beforeMatch >= 0) {
                    dp[i] += dp[beforeMatch];
                }
            }
            
            maxLen = Math.max(maxLen, dp[i]);
        }
    }
    
    return maxLen;
}

// Example: s = "()(()"
// Index:  0 1 2 3 4
// Char:   ( ) ( ( )
// dp:     0 2 0 0 2
// 
// i=1: s[1]=')' and s[0]='(' -> dp[1] = 0 + 2 = 2
// i=4: s[4]=')' and s[3]='(' -> dp[4] = 0 + 2 = 2
// Max: 2

// Example: s = "()(())"
// Index:  0 1 2 3 4 5
// Char:   ( ) ( ( ) )
// dp:     0 2 0 0 2 6
// 
// i=1: '()' -> dp[1] = 2
// i=4: '()' inside -> dp[4] = 2
// i=5: '))' -> dp[5] = dp[4] + 2 + dp[1] = 2 + 2 + 2 = 6
// Max: 6

Example: Stack approach

function longestValidParenthesesStack(s) {
    const stack = [-1]; // Base for valid substring
    let maxLen = 0;
    
    for (let i = 0; i < s.length; i++) {
        if (s[i] === '(') {
            stack.push(i);
        } else {
            stack.pop();
            
            if (stack.length === 0) {
                // No matching '(' - new base
                stack.push(i);
            } else {
                // Valid substring from stack.top to i
                maxLen = Math.max(maxLen, i - stack[stack.length - 1]);
            }
        }
    }
    
    return maxLen;
}

// Example: s = "(()"
// i=0, '(': stack = [-1, 0]
// i=1, '(': stack = [-1, 0, 1]
// i=2, ')': pop 1, length = 2 - 0 = 2, stack = [-1, 0]
// Max: 2

// Stack stores indices of unmatched brackets
// Length = current index - index before unmatched portion

Example: Two-pass O(1) space

function longestValidParenthesesConstant(s) {
    let left = 0, right = 0, maxLen = 0;
    
    // Left to right pass
    for (let i = 0; i < s.length; i++) {
        if (s[i] === '(') {
            left++;
        } else {
            right++;
        }
        
        if (left === right) {
            maxLen = Math.max(maxLen, 2 * right);
        } else if (right > left) {
            left = right = 0; // Reset
        }
    }
    
    left = right = 0;
    
    // Right to left pass
    for (let i = s.length - 1; i >= 0; i--) {
        if (s[i] === '(') {
            left++;
        } else {
            right++;
        }
        
        if (left === right) {
            maxLen = Math.max(maxLen, 2 * left);
        } else if (left > right) {
            left = right = 0; // Reset
        }
    }
    
    return maxLen;
}

// Two passes handle cases where extra brackets are on different sides
// Left-to-right: handles extra '(' at end
// Right-to-left: handles extra '(' at start
// O(1) space, O(n) time

4. Remove Invalid Parentheses (BFS)

Approach Method Time Space
BFS Level-by-level Remove one char at a time, check validity O(2^n) O(2^n)
Backtracking Count invalid brackets, try removing O(2^n) O(n) depth
Early Termination Stop when first valid strings found BFS guarantees minimum removals Key optimization
Deduplication Use Set to avoid duplicate processing Critical for performance Prevents exponential blowup

Example: BFS with early termination

function removeInvalidParentheses(s) {
    const result = [];
    const visited = new Set();
    const queue = [s];
    visited.add(s);
    let found = false;
    
    function isValid(str) {
        let count = 0;
        for (const char of str) {
            if (char === '(') count++;
            else if (char === ')') {
                count--;
                if (count < 0) return false;
            }
        }
        return count === 0;
    }
    
    while (queue.length > 0) {
        const current = queue.shift();
        
        if (isValid(current)) {
            result.push(current);
            found = true;
        }
        
        if (found) continue; // Don't generate more levels
        
        // Generate all possible states by removing one bracket
        for (let i = 0; i < current.length; i++) {
            // Only remove parentheses
            if (current[i] !== '(' && current[i] !== ')') {
                continue;
            }
            
            const next = current.slice(0, i) + current.slice(i + 1);
            
            if (!visited.has(next)) {
                visited.add(next);
                queue.push(next);
            }
        }
    }
    
    return result.length > 0 ? result : [''];
}

// Example: s = "()())()"
// Level 0: "()())()" (invalid)
// Level 1: Remove one bracket, check each
// First valid found at minimal removal
// Result: ["(())()","()()()"]

// BFS ensures minimum removals by checking level-by-level

Example: Backtracking with counting

function removeInvalidParenthesesBacktrack(s) {
    const result = [];
    
    // Count invalid brackets
    let leftRemove = 0, rightRemove = 0;
    for (const char of s) {
        if (char === '(') {
            leftRemove++;
        } else if (char === ')') {
            if (leftRemove > 0) {
                leftRemove--;
            } else {
                rightRemove++;
            }
        }
    }
    
    function backtrack(index, leftRem, rightRem, 
                       leftCount, rightCount, current) {
        if (index === s.length) {
            if (leftRem === 0 && rightRem === 0) {
                result.push(current);
            }
            return;
        }
        
        const char = s[index];
        
        // Option 1: Remove this bracket
        if ((char === '(' && leftRem > 0) || 
            (char === ')' && rightRem > 0)) {
            backtrack(
                index + 1,
                leftRem - (char === '(' ? 1 : 0),
                rightRem - (char === ')' ? 1 : 0),
                leftCount,
                rightCount,
                current
            );
        }
        
        // Option 2: Keep this character
        if (char !== '(' && char !== ')') {
            // Regular character - always keep
            backtrack(index + 1, leftRem, rightRem, 
                     leftCount, rightCount, current + char);
        } else if (char === '(') {
            // Keep opening bracket
            backtrack(index + 1, leftRem, rightRem, 
                     leftCount + 1, rightCount, current + char);
        } else if (rightCount < leftCount) {
            // Keep closing bracket if valid
            backtrack(index + 1, leftRem, rightRem, 
                     leftCount, rightCount + 1, current + char);
        }
    }
    
    backtrack(0, leftRemove, rightRemove, 0, 0, '');
    return [...new Set(result)]; // Deduplicate
}

// Pre-compute how many brackets need removal
// Backtrack with constraints to generate only valid removals

5. Minimum Add to Make Valid Parentheses

Approach Method Time Space
Counter Method Track unmatched opening and closing O(n) O(1)
Stack Count Use stack size + extra counter O(n) O(n)
Balance Tracking Maintain running balance, count negatives O(n) O(1)
Two Variables open (unmatched '('), close (unmatched ')') Simplest approach Recommended

Example: Counter approach

function minAddToMakeValid(s) {
    let open = 0;   // Unmatched '('
    let close = 0;  // Unmatched ')'
    
    for (const char of s) {
        if (char === '(') {
            open++;
        } else {
            if (open > 0) {
                open--; // Match with previous '('
            } else {
                close++; // No '(' to match
            }
        }
    }
    
    // open: need ')' to close
    // close: need '(' to open
    return open + close;
}

// Example: s = "())"
// '(' -> open=1
// ')' -> open=0 (matched)
// ')' -> close=1 (unmatched)
// Result: 0 + 1 = 1 (need one '(')

// Example: s = "((("
// open=3 at end
// Result: 3 (need three ')')

Example: Balance tracking

function minAddToMakeValidBalance(s) {
    let balance = 0;
    let additions = 0;
    
    for (const char of s) {
        if (char === '(') {
            balance++;
        } else {
            balance--;
        }
        
        // If balance goes negative, need '(' before
        if (balance < 0) {
            additions++;
            balance = 0; // Reset after adding '('
        }
    }
    
    // balance = unmatched '(' at end
    return additions + balance;
}

// Example: s = "))(("
// ')' -> balance=-1 -> additions=1, reset
// ')' -> balance=-1 -> additions=2, reset
// '(' -> balance=1
// '(' -> balance=2
// Result: 2 + 2 = 4

// additions: '(' needed before
// balance: ')' needed after
// Related: Minimum number of swaps to make valid
// Only '(' and ')' with equal count
function minSwapsToMakeValid(s) {
    let open = 0;
    let unbalanced = 0;
    
    for (const char of s) {
        if (char === '(') {
            open++;
        } else {
            if (open > 0) {
                open--;
            } else {
                unbalanced++;
            }
        }
    }
    
    // Number of swaps = ceil(unbalanced / 2)
    return Math.ceil(unbalanced / 2);
}

// Example: s = "))(("
// unbalanced = 2 (two ')' before any '(')
// Swaps needed: ceil(2/2) = 1
// Swap positions 0 and 2: "()()"

// Each swap fixes two unbalanced brackets

Summary: Parentheses Problems Patterns

  • Valid Parentheses: Stack of opening brackets, pop and match on closing, valid if stack empty at end - O(n) time, O(n) space
  • Stack Pattern: Push '(' '{' '[', on ')' '}' ']' pop and verify match, HashMap maps closing to opening for quick lookup
  • Single Type Optimization: Use counter instead of stack for single bracket type - O(1) space
  • Generate Parentheses: Backtracking with constraints: add '(' if open < n, add ')' if close < open - generates all valid combinations
  • Catalan Number: Number of valid n-pair parentheses = $C_n = \frac{1}{n+1}\binom{2n}{n}$ - fundamental counting result
  • Longest Valid DP: dp[i] = length of valid substring ending at i, handle '()' and '))' cases separately - O(n) time and space
  • DP Transitions: If s[i]=')' and s[i-1]='(': dp[i] = dp[i-2] + 2; if '))': check matching, add previous valid lengths
  • Stack for Longest: Track indices of unmatched brackets, length = current - index before unmatched portion - O(n) time/space
  • Two-Pass O(1): Left-to-right and right-to-left with counters, reset when imbalance - handles extra brackets on either side
  • Remove Invalid BFS: Level-by-level removal, stop at first valid level (minimum removals), use Set for deduplication
  • BFS Early Stop: Once valid strings found, don't explore deeper levels - guarantees minimum removal with BFS
  • Remove Backtracking: Pre-count invalid brackets, backtrack with removal budget, prune invalid paths during construction
  • Minimum Add: Track unmatched opening (need ')') and closing (need '('), sum is answer - O(n) time, O(1) space
  • Balance Method: Run counter, when negative add '(' and reset, final balance is unmatched '(' needing ')'