🧠
Backtracking with Pruning and Early Validity Checks
💡 This approach improves brute force by pruning invalid paths early and avoiding duplicate work, making it feasible for moderate input sizes.
Intuition
Use backtracking to remove parentheses only when necessary, track counts of left and right parentheses removed, and prune paths that cannot lead to valid strings.
Algorithm
- Calculate the minimum number of left and right parentheses to remove.
- Use backtracking to try removing parentheses only when needed.
- Keep track of counts of parentheses to ensure validity during construction.
- Avoid duplicates by skipping consecutive identical parentheses removals.
💡 The key difficulty is managing counts and pruning to avoid exponential blowup and duplicates.
from typing import List
def remove_invalid_parentheses(s: str) -> List[str]:
res = set()
# Calculate minimum removals
left_remove, right_remove = 0, 0
for c in s:
if c == '(': left_remove += 1
elif c == ')':
if left_remove > 0:
left_remove -= 1
else:
right_remove += 1
def backtrack(index: int, path: str, left_count: int, right_count: int, left_remove: int, right_remove: int):
if index == len(s):
if left_remove == 0 and right_remove == 0:
res.add(path)
return
c = s[index]
if c == '(' and left_remove > 0:
backtrack(index + 1, path, left_count, right_count, left_remove - 1, right_remove)
elif c == ')' and right_remove > 0:
backtrack(index + 1, path, left_count, right_count, left_remove, right_remove - 1)
# Keep current char
if c not in ('(', ')'):
backtrack(index + 1, path + c, left_count, right_count, left_remove, right_remove)
elif c == '(': # add '('
backtrack(index + 1, path + c, left_count + 1, right_count, left_remove, right_remove)
elif c == ')' and right_count < left_count: # add ')' only if valid
backtrack(index + 1, path + c, left_count, right_count + 1, left_remove, right_remove)
backtrack(0, "", 0, 0, left_remove, right_remove)
return list(res)
# Driver code
if __name__ == "__main__":
test_str = "()())()"
print(remove_invalid_parentheses(test_str))
Line Notes
left_remove, right_remove = 0, 0Count minimum parentheses to remove to fix imbalance
if left_remove > 0:Try removing '(' only if we have removals left
if c not in ('(', ')'):Always keep non-parenthesis characters
elif c == ')' and right_count < left_count:Add ')' only if it won't invalidate string
import java.util.*;
public class RemoveInvalidParentheses {
public static List<String> removeInvalidParentheses(String s) {
Set<String> res = new HashSet<>();
int leftRemove = 0, rightRemove = 0;
for (char c : s.toCharArray()) {
if (c == '(') leftRemove++;
else if (c == ')') {
if (leftRemove > 0) leftRemove--;
else rightRemove++;
}
}
backtrack(s, 0, new StringBuilder(), 0, 0, leftRemove, rightRemove, res);
return new ArrayList<>(res);
}
private static void backtrack(String s, int index, StringBuilder path, int leftCount, int rightCount, int leftRemove, int rightRemove, Set<String> res) {
if (index == s.length()) {
if (leftRemove == 0 && rightRemove == 0) {
res.add(path.toString());
}
return;
}
char c = s.charAt(index);
if (c == '(' && leftRemove > 0) {
backtrack(s, index + 1, path, leftCount, rightCount, leftRemove - 1, rightRemove, res);
} else if (c == ')' && rightRemove > 0) {
backtrack(s, index + 1, path, leftCount, rightCount, leftRemove, rightRemove - 1, res);
}
path.append(c);
if (c != '(' && c != ')') {
backtrack(s, index + 1, path, leftCount, rightCount, leftRemove, rightRemove, res);
} else if (c == '(') {
backtrack(s, index + 1, path, leftCount + 1, rightCount, leftRemove, rightRemove, res);
} else if (c == ')' && rightCount < leftCount) {
backtrack(s, index + 1, path, leftCount, rightCount + 1, leftRemove, rightRemove, res);
}
path.deleteCharAt(path.length() - 1);
}
public static void main(String[] args) {
String test = "()())()";
System.out.println(removeInvalidParentheses(test));
}
}
Line Notes
int leftRemove = 0, rightRemove = 0;Calculate minimum removals needed
if (c == '(' && leftRemove > 0)Try removing '(' only if needed
path.append(c);Add current character to path before recursion
path.deleteCharAt(path.length() - 1);Backtrack by removing last character
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
using namespace std;
void backtrack(const string& s, int index, string& path, int leftCount, int rightCount, int leftRemove, int rightRemove, unordered_set<string>& res) {
if (index == (int)s.size()) {
if (leftRemove == 0 && rightRemove == 0) {
res.insert(path);
}
return;
}
char c = s[index];
if (c == '(' && leftRemove > 0) {
backtrack(s, index + 1, path, leftCount, rightCount, leftRemove - 1, rightRemove, res);
} else if (c == ')' && rightRemove > 0) {
backtrack(s, index + 1, path, leftCount, rightCount, leftRemove, rightRemove - 1, res);
}
path.push_back(c);
if (c != '(' && c != ')') {
backtrack(s, index + 1, path, leftCount, rightCount, leftRemove, rightRemove, res);
} else if (c == '(') {
backtrack(s, index + 1, path, leftCount + 1, rightCount, leftRemove, rightRemove, res);
} else if (c == ')' && rightCount < leftCount) {
backtrack(s, index + 1, path, leftCount, rightCount + 1, leftRemove, rightRemove, res);
}
path.pop_back();
}
vector<string> removeInvalidParentheses(string s) {
int leftRemove = 0, rightRemove = 0;
for (char c : s) {
if (c == '(') leftRemove++;
else if (c == ')') {
if (leftRemove > 0) leftRemove--;
else rightRemove++;
}
}
unordered_set<string> res;
string path;
backtrack(s, 0, path, 0, 0, leftRemove, rightRemove, res);
return vector<string>(res.begin(), res.end());
}
int main() {
string test = "()())()";
vector<string> result = removeInvalidParentheses(test);
for (auto& str : result) {
cout << str << endl;
}
return 0;
}
Line Notes
int leftRemove = 0, rightRemove = 0;Count minimum parentheses to remove
if (c == '(' && leftRemove > 0)Try removing '(' only if removal quota remains
path.push_back(c);Add character before recursive call
path.pop_back();Backtrack to explore other options
function removeInvalidParentheses(s) {
let leftRemove = 0, rightRemove = 0;
for (let c of s) {
if (c === '(') leftRemove++;
else if (c === ')') {
if (leftRemove > 0) leftRemove--;
else rightRemove++;
}
}
const res = new Set();
function backtrack(index, path, leftCount, rightCount, leftRemove, rightRemove) {
if (index === s.length) {
if (leftRemove === 0 && rightRemove === 0) {
res.add(path);
}
return;
}
const c = s[index];
if (c === '(' && leftRemove > 0) {
backtrack(index + 1, path, leftCount, rightCount, leftRemove - 1, rightRemove);
} else if (c === ')' && rightRemove > 0) {
backtrack(index + 1, path, leftCount, rightCount, leftRemove, rightRemove - 1);
}
if (c !== '(' && c !== ')') {
backtrack(index + 1, path + c, leftCount, rightCount, leftRemove, rightRemove);
} else if (c === '(') {
backtrack(index + 1, path + c, leftCount + 1, rightCount, leftRemove, rightRemove);
} else if (c === ')' && rightCount < leftCount) {
backtrack(index + 1, path + c, leftCount, rightCount + 1, leftRemove, rightRemove);
}
}
backtrack(0, "", 0, 0, leftRemove, rightRemove);
return Array.from(res);
}
// Test
console.log(removeInvalidParentheses("()())()"));
Line Notes
let leftRemove = 0, rightRemove = 0;Calculate minimum removals needed
if (c === '(' && leftRemove > 0)Try removing '(' only if quota remains
res.add(path);Add valid string to results set to avoid duplicates
backtrack(index + 1, path + c, leftCount + 1, rightCount, leftRemove, rightRemove);Add '(' and update count
TimeO(2^n)
SpaceO(n) recursion stack + O(2^n) result storage
Pruning reduces the search space compared to brute force, but worst case remains exponential.
💡 For n=20, pruning helps but still may explore many paths; practical for interview but not huge inputs.
Interview Verdict: Accepted / Practical for interviews
This approach balances clarity and efficiency, making it a good coding choice in interviews.