Skip to main content

Remove Invalid Parentheses

Given a string s, remove the minimum number of parentheses to make s valid. Return all possible results.

Note: A valid string has balanced parentheses (every opening parenthesis has a matching closing parenthesis).

Example(s)​

Example 1:

Input: s = "(()()()"
Output: ["()()()","(())()","(()())"]
Explanation:
The string has 4 opening and 3 closing parentheses.
We need to remove 1 opening parenthesis.
Removing at different positions gives different valid strings.

Example 2:

Input: s = "()()()"
Output: ["()()()"]
Explanation:
The string is already valid, so no removals needed.

Example 3:

Input: s = "()())()"
Output: ["()()()", "(())()"]
Explanation:
We need to remove 1 closing parenthesis.
Removing the first ')' at index 2 or the second ')' at index 3.

Example 4:

Input: s = "(a)())()"
Output: ["(a)()()", "(a())()"]
Explanation:
We need to remove 1 closing parenthesis.

Solution​

The solution uses backtracking with two passes:

  1. First pass: Count unmatched opening parentheses (need to remove some '(')
  2. Second pass: Count unmatched closing parentheses (need to remove some ')')
  3. Backtracking: Generate all combinations of removing the unmatched parentheses
  4. Validation: Check if the resulting string is valid
  5. Deduplication: Use a set to avoid duplicate results

JavaScript Solution

/**
* Remove minimum parentheses to make string valid
* @param {string} s - Input string
* @return {string[]} - All possible valid strings
*/
function removeInvalidParentheses(s) {
const result = new Set();

// Count how many '(' and ')' we need to remove
let leftRem = 0;  // Unmatched opening parentheses
let rightRem = 0;  // Unmatched closing parentheses

// First pass: count unmatched opening parentheses
for (let char of s) {
  if (char === '(') {
    leftRem++;
  } else if (char === ')') {
    if (leftRem > 0) {
      leftRem--;  // Matched
    } else {
      rightRem++;  // Unmatched
    }
  }
}

/**
 * Backtracking function to generate all valid strings
 * @param {string} str - Current string being built
 * @param {number} index - Current index in original string
 * @param {number} leftCount - Number of '(' used so far
 * @param {number} rightCount - Number of ')' used so far
 * @param {number} leftRem - Remaining '(' to remove
 * @param {number} rightRem - Remaining ')' to remove
 */
function backtrack(str, index, leftCount, rightCount, leftRem, rightRem) {
  // Base case: processed entire string
  if (index === s.length) {
    // Valid if balanced and no more removals needed
    if (leftRem === 0 && rightRem === 0 && leftCount === rightCount) {
      result.add(str);
    }
    return;
  }
  
  const char = s[index];
  
  // Case 1: Skip this character (remove it)
  if ((char === '(' && leftRem > 0) || (char === ')' && rightRem > 0)) {
    backtrack(
      str,
      index + 1,
      leftCount,
      rightCount,
      leftRem - (char === '(' ? 1 : 0),
      rightRem - (char === ')' ? 1 : 0)
    );
  }
  
  // Case 2: Keep this character
  if (char !== '(' && char !== ')') {
    // Regular character, always keep
    backtrack(str + char, index + 1, leftCount, rightCount, leftRem, rightRem);
  } else if (char === '(') {
    // Opening parenthesis
    backtrack(str + char, index + 1, leftCount + 1, rightCount, leftRem, rightRem);
  } else if (char === ')' && rightCount < leftCount) {
    // Closing parenthesis (only if we have matching opening)
    backtrack(str + char, index + 1, leftCount, rightCount + 1, leftRem, rightRem);
  }
}

backtrack('', 0, 0, 0, leftRem, rightRem);
return Array.from(result);
}

// Test cases
console.log('Example 1:', removeInvalidParentheses("(()()()")); 
// ["()()()","(())()","(()())"]

console.log('Example 2:', removeInvalidParentheses("()()()")); 
// ["()()()"]

console.log('Example 3:', removeInvalidParentheses("()())()")); 
// ["()()()", "(())()"]

console.log('Example 4:', removeInvalidParentheses("(a)())()")); 
// ["(a)()()", "(a())()"]
Output:
Click "Run Code" to execute the code and see the results.

Alternative Solution (Optimized with Pruning)​

Here's an optimized version with better pruning:

/**
* Optimized version with better pruning
*/
function removeInvalidParenthesesOptimized(s) {
const result = new Set();

// Count removals needed
let leftRem = 0, rightRem = 0;
for (let char of s) {
if (char === '(') leftRem++;
else if (char === ')') {
if (leftRem > 0) leftRem--;
else rightRem++;
}
}

function isValid(str) {
let count = 0;
for (let char of str) {
if (char === '(') count++;
else if (char === ')') {
count--;
if (count < 0) return false;
}
}
return count === 0;
}

function backtrack(str, index, leftRem, rightRem, open) {
if (leftRem < 0 || rightRem < 0 || open < 0) return;

if (index === s.length) {
if (leftRem === 0 && rightRem === 0 && isValid(str)) {
result.add(str);
}
return;
}

const char = s[index];

if (char === '(') {
// Remove this '('
if (leftRem > 0) {
backtrack(str, index + 1, leftRem - 1, rightRem, open);
}
// Keep this '('
backtrack(str + char, index + 1, leftRem, rightRem, open + 1);
} else if (char === ')') {
// Remove this ')'
if (rightRem > 0) {
backtrack(str, index + 1, leftRem, rightRem - 1, open);
}
// Keep this ')' (only if we have open '(')
if (open > 0) {
backtrack(str + char, index + 1, leftRem, rightRem, open - 1);
}
} else {
// Regular character, always keep
backtrack(str + char, index + 1, leftRem, rightRem, open);
}
}

backtrack('', 0, leftRem, rightRem, 0);
return Array.from(result);
}

Complexity​

  • Time Complexity: O(2^n) in the worst case, where n is the length of the string. However, with pruning, it's typically much better in practice.
  • Space Complexity: O(n) for the recursion stack, plus O(k) where k is the number of valid results.

Approach​

The solution uses backtracking with two phases:

  1. Count phase: Determine how many '(' and ')' need to be removed

    • Count unmatched opening parentheses
    • Count unmatched closing parentheses
  2. Backtracking phase: Generate all combinations

    • For each character, decide whether to keep or remove it
    • Prune invalid branches early
    • Track balance of parentheses
    • Collect valid results
  3. Key optimizations:

    • Only remove the minimum number needed
    • Skip consecutive duplicates to avoid duplicate results
    • Early pruning when balance becomes negative

Key Insights​

  • Two-pass counting: First determine what needs to be removed
  • Backtracking: Generate all combinations of removals
  • Pruning: Early termination of invalid branches
  • Deduplication: Use a set to avoid duplicate results
  • Balance tracking: Ensure parentheses remain balanced during construction

Step-by-Step Example​

Let's trace through Example 1: s = "(()()()"

Step 1: Count removals needed
s = "(()()()"
leftRem = 0, rightRem = 0
Process: '(' β†’ leftRem=1
'(' β†’ leftRem=2
')' β†’ leftRem=1
'(' β†’ leftRem=2
')' β†’ leftRem=1
'(' β†’ leftRem=2
')' β†’ leftRem=1
Result: leftRem=1, rightRem=0 (need to remove 1 '(')

Step 2: Backtracking
Try removing '(' at different positions:
- Remove index 0: "()()()" βœ“
- Remove index 1: "()()()" (duplicate)
- Remove index 3: "(())()" βœ“
- Remove index 5: "(()())" βœ“

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

Edge Cases​

  • Already valid: Return the original string
  • All opening: Remove all opening parentheses
  • All closing: Remove all closing parentheses
  • Empty string: Return [""]
  • No parentheses: Return the original string
  • Consecutive duplicates: Handle carefully to avoid duplicate results

Optimization: Skip Consecutive Duplicates​

To avoid duplicate results when there are consecutive identical parentheses:

// In backtrack function, add this check:
if (index > 0 && s[index] === s[index - 1]) {
// Skip if we already tried removing this character
// (when previous character was also removed)
}
  • Valid Parentheses: Check if parentheses are balanced
  • Generate Parentheses: Generate all valid parentheses combinations
  • Longest Valid Parentheses: Find longest valid substring
  • Minimum Remove to Make Valid: Return just one valid string (simpler)

Takeaways​

  • Backtracking is powerful for generating all combinations
  • Two-phase approach (count then generate) is efficient
  • Pruning is crucial for performance
  • Deduplication is important when generating all results
  • Balance tracking ensures we maintain valid parentheses throughout