Remove Invalid Parentheses
Imagine you receive a corrupted string of parentheses from a user input or a legacy system, and you need to clean it up by removing the minimum number of invalid parentheses to make it valid again.
Given a string s that contains parentheses and letters, remove the minimum number of invalid parentheses to make the input string valid. Return all possible results in any order. A string is valid if parentheses are balanced and properly closed.
1 ≤ s.length ≤ 10^5s consists of lowercase English letters and parentheses '(' and ')'."()())()"["()()()", "(())()"]Removing one invalid ')' at different positions yields two valid strings.
"(a)())()"["(a)()()", "(a())()"]Letters are ignored in validity checks; removing one invalid ')' yields two valid strings.
")("[""]No valid parentheses can be formed, so empty string is the only valid result.
- Empty string → returns [""]
- String with no parentheses → returns the string itself
- String with all valid parentheses → returns the string itself
- String with only invalid parentheses → returns [""]
Excessive recursion or BFS states causing TLE
✅ Add checks to prune paths where closing parentheses exceed opening ones
Output contains duplicates, or performance degrades due to repeated states
✅ Use a set or hash map to track visited strings or results
Generates invalid strings, increasing search space
✅ Only add ')' if rightCount < leftCount during backtracking
Incorrect results or missing valid strings
✅ Only remove '(' or ')' characters during generation
Intuition
Try removing every possible combination of parentheses and check if the resulting string is valid. Collect all valid strings with minimum removals.
Algorithm
- Generate all possible subsequences by removing parentheses in all combinations.
- Check each subsequence for validity of parentheses.
- Track the minimum number of removals needed to get a valid string.
- Return all valid strings with minimum removals.
from typing import List
def is_valid(s: str) -> bool:
count = 0
for c in s:
if c == '(': count += 1
elif c == ')':
count -= 1
if count < 0:
return False
return count == 0
def remove_invalid_parentheses(s: str) -> List[str]:
res = []
min_removed = float('inf')
def backtrack(index: int, path: str, removed: int):
nonlocal min_removed
if index == len(s):
if is_valid(path):
if removed < min_removed:
min_removed = removed
res.clear()
res.append(path)
elif removed == min_removed:
res.append(path)
return
# Option 1: remove current char if it's a parenthesis
if s[index] in ('(', ')'):
backtrack(index + 1, path, removed + 1)
# Option 2: keep current char
backtrack(index + 1, path + s[index], removed)
backtrack(0, "", 0)
return res
# Driver code
if __name__ == "__main__":
test_str = "()())()"
print(remove_invalid_parentheses(test_str))def is_valid(s: str) -> bool:Defines a helper to check if parentheses are balancedif count < 0:Early exit if closing parenthesis exceeds opening onesdef backtrack(index: int, path: str, removed: int):Recursive function exploring all subsequencesif removed < min_removed:Update minimum removals and reset results when better foundimport java.util.*;
public class RemoveInvalidParentheses {
public static boolean isValid(String s) {
int count = 0;
for (char c : s.toCharArray()) {
if (c == '(') count++;
else if (c == ')') {
count--;
if (count < 0) return false;
}
}
return count == 0;
}
public static List<String> removeInvalidParentheses(String s) {
List<String> res = new ArrayList<>();
int[] minRemoved = {Integer.MAX_VALUE};
backtrack(s, 0, new StringBuilder(), 0, minRemoved, res);
return res;
}
private static void backtrack(String s, int index, StringBuilder path, int removed, int[] minRemoved, List<String> res) {
if (index == s.length()) {
if (isValid(path.toString())) {
if (removed < minRemoved[0]) {
minRemoved[0] = removed;
res.clear();
res.add(path.toString());
} else if (removed == minRemoved[0]) {
res.add(path.toString());
}
}
return;
}
char c = s.charAt(index);
if (c == '(' || c == ')') {
backtrack(s, index + 1, path, removed + 1, minRemoved, res);
}
path.append(c);
backtrack(s, index + 1, path, removed, minRemoved, res);
path.deleteCharAt(path.length() - 1);
}
public static void main(String[] args) {
String test = "()())()";
System.out.println(removeInvalidParentheses(test));
}
}public static boolean isValid(String s)Helper to check balanced parenthesesif (count < 0) return false;Early invalid detection to prunebacktrack(s, index + 1, path, removed + 1, minRemoved, res);Try removing current parenthesispath.deleteCharAt(path.length() - 1);Backtrack by removing last added char#include <iostream>
#include <vector>
#include <string>
#include <climits>
using namespace std;
bool isValid(const string& s) {
int count = 0;
for (char c : s) {
if (c == '(') count++;
else if (c == ')') {
count--;
if (count < 0) return false;
}
}
return count == 0;
}
void backtrack(const string& s, int index, string& path, int removed, int& minRemoved, vector<string>& res) {
if (index == (int)s.size()) {
if (isValid(path)) {
if (removed < minRemoved) {
minRemoved = removed;
res.clear();
res.push_back(path);
} else if (removed == minRemoved) {
res.push_back(path);
}
}
return;
}
if (s[index] == '(' || s[index] == ')') {
backtrack(s, index + 1, path, removed + 1, minRemoved, res);
}
path.push_back(s[index]);
backtrack(s, index + 1, path, removed, minRemoved, res);
path.pop_back();
}
vector<string> removeInvalidParentheses(string s) {
vector<string> res;
int minRemoved = INT_MAX;
string path;
backtrack(s, 0, path, 0, minRemoved, res);
return res;
}
int main() {
string test = "()())()";
vector<string> result = removeInvalidParentheses(test);
for (auto& str : result) {
cout << str << endl;
}
return 0;
}bool isValid(const string& s)Checks if parentheses are balancedif (count < 0) return false;Detect invalid early to avoid unnecessary workbacktrack(s, index + 1, path, removed + 1, minRemoved, res);Try removing current parenthesispath.pop_back();Undo last addition to explore other pathsfunction isValid(s) {
let count = 0;
for (let c of s) {
if (c === '(') count++;
else if (c === ')') {
count--;
if (count < 0) return false;
}
}
return count === 0;
}
function removeInvalidParentheses(s) {
const res = [];
let minRemoved = Infinity;
function backtrack(index, path, removed) {
if (index === s.length) {
if (isValid(path)) {
if (removed < minRemoved) {
minRemoved = removed;
res.length = 0;
res.push(path);
} else if (removed === minRemoved) {
res.push(path);
}
}
return;
}
if (s[index] === '(' || s[index] === ')') {
backtrack(index + 1, path, removed + 1);
}
backtrack(index + 1, path + s[index], removed);
}
backtrack(0, "", 0);
return res;
}
// Test
console.log(removeInvalidParentheses("()())()"));function isValid(s)Checks if parentheses are balancedif (count < 0) return false;Early invalid detection to prune searchbacktrack(index + 1, path, removed + 1);Try removing current parenthesisres.length = 0;Clear previous results when better minimum foundO(2^n * n)O(n) recursion stack + O(2^n) result storageWe explore all subsequences (2^n) and check validity (O(n)) for each, leading to exponential time.
This approach is too slow for large inputs but is essential to understand the problem's search space and motivate pruning.
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.
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))left_remove, right_remove = 0, 0Count minimum parentheses to remove to fix imbalanceif left_remove > 0:Try removing '(' only if we have removals leftif c not in ('(', ')'):Always keep non-parenthesis characterselif c == ')' and right_count < left_count:Add ')' only if it won't invalidate stringimport 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));
}
}int leftRemove = 0, rightRemove = 0;Calculate minimum removals neededif (c == '(' && leftRemove > 0)Try removing '(' only if neededpath.append(c);Add current character to path before recursionpath.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;
}int leftRemove = 0, rightRemove = 0;Count minimum parentheses to removeif (c == '(' && leftRemove > 0)Try removing '(' only if removal quota remainspath.push_back(c);Add character before recursive callpath.pop_back();Backtrack to explore other optionsfunction 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("()())()"));let leftRemove = 0, rightRemove = 0;Calculate minimum removals neededif (c === '(' && leftRemove > 0)Try removing '(' only if quota remainsres.add(path);Add valid string to results set to avoid duplicatesbacktrack(index + 1, path + c, leftCount + 1, rightCount, leftRemove, rightRemove);Add '(' and update countO(2^n)O(n) recursion stack + O(2^n) result storagePruning reduces the search space compared to brute force, but worst case remains exponential.
This approach balances clarity and efficiency, making it a good coding choice in interviews.
Intuition
Start from the original string and generate all strings by removing one parenthesis at a time. Use BFS to explore level by level until valid strings are found.
Algorithm
- Initialize a queue with the original string and a visited set.
- While queue not empty, process all strings at current level.
- For each string, check if valid; if yes, add to results and stop further deeper levels.
- If not valid, generate all possible strings by removing one parenthesis and enqueue unseen ones.
- Return all valid strings found at the first valid level.
from typing import List
from collections import deque
def is_valid(s: str) -> bool:
count = 0
for c in s:
if c == '(': count += 1
elif c == ')':
count -= 1
if count < 0:
return False
return count == 0
def remove_invalid_parentheses_bfs(s: str) -> List[str]:
res = []
visited = set([s])
queue = deque([s])
found = False
while queue:
size = len(queue)
for _ in range(size):
curr = queue.popleft()
if is_valid(curr):
res.append(curr)
found = True
if found:
continue
for i in range(len(curr)):
if curr[i] not in ('(', ')'):
continue
next_str = curr[:i] + curr[i+1:]
if next_str not in visited:
visited.add(next_str)
queue.append(next_str)
if found:
break
return res
# Driver code
if __name__ == "__main__":
test_str = "()())()"
print(remove_invalid_parentheses_bfs(test_str))visited = set([s])Track visited strings to avoid repeatsif is_valid(curr):Check if current string is valid parenthesesfor i in range(len(curr))Try removing each parenthesis onceif found: breakStop BFS once valid strings found at current levelimport java.util.*;
public class RemoveInvalidParentheses {
public static boolean isValid(String s) {
int count = 0;
for (char c : s.toCharArray()) {
if (c == '(') count++;
else if (c == ')') {
count--;
if (count < 0) return false;
}
}
return count == 0;
}
public static List<String> removeInvalidParenthesesBFS(String s) {
List<String> res = new ArrayList<>();
Set<String> visited = new HashSet<>();
Queue<String> queue = new LinkedList<>();
visited.add(s);
queue.offer(s);
boolean found = false;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
String curr = queue.poll();
if (isValid(curr)) {
res.add(curr);
found = true;
}
if (found) continue;
for (int j = 0; j < curr.length(); j++) {
if (curr.charAt(j) != '(' && curr.charAt(j) != ')') continue;
String next = curr.substring(0, j) + curr.substring(j + 1);
if (!visited.contains(next)) {
visited.add(next);
queue.offer(next);
}
}
}
if (found) break;
}
return res;
}
public static void main(String[] args) {
String test = "()())()";
System.out.println(removeInvalidParenthesesBFS(test));
}
}Set<String> visited = new HashSet<>();Avoid revisiting same stringsif (isValid(curr)) {Check validity at current BFS levelfor (int j = 0; j < curr.length(); j++) {Generate next states by removing one parenthesisif (found) break;Stop BFS after first valid level#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unordered_set>
using namespace std;
bool isValid(const string& s) {
int count = 0;
for (char c : s) {
if (c == '(') count++;
else if (c == ')') {
count--;
if (count < 0) return false;
}
}
return count == 0;
}
vector<string> removeInvalidParenthesesBFS(string s) {
vector<string> res;
unordered_set<string> visited;
queue<string> q;
visited.insert(s);
q.push(s);
bool found = false;
while (!q.empty()) {
int size = q.size();
for (int i = 0; i < size; i++) {
string curr = q.front(); q.pop();
if (isValid(curr)) {
res.push_back(curr);
found = true;
}
if (found) continue;
for (int j = 0; j < (int)curr.size(); j++) {
if (curr[j] != '(' && curr[j] != ')') continue;
string next = curr.substr(0, j) + curr.substr(j + 1);
if (visited.find(next) == visited.end()) {
visited.insert(next);
q.push(next);
}
}
}
if (found) break;
}
return res;
}
int main() {
string test = "()())()";
vector<string> result = removeInvalidParenthesesBFS(test);
for (auto& str : result) {
cout << str << endl;
}
return 0;
}unordered_set<string> visited;Track visited strings to prevent repeatsif (isValid(curr)) {Check if current string is validfor (int j = 0; j < (int)curr.size(); j++) {Generate next states by removing one parenthesisif (found) break;Stop BFS after finding valid strings at current levelfunction isValid(s) {
let count = 0;
for (let c of s) {
if (c === '(') count++;
else if (c === ')') {
count--;
if (count < 0) return false;
}
}
return count === 0;
}
function removeInvalidParenthesesBFS(s) {
const res = [];
const visited = new Set([s]);
const queue = [s];
let found = false;
while (queue.length > 0) {
const size = queue.length;
for (let i = 0; i < size; i++) {
const curr = queue.shift();
if (isValid(curr)) {
res.push(curr);
found = true;
}
if (found) continue;
for (let j = 0; j < curr.length; j++) {
if (curr[j] !== '(' && curr[j] !== ')') continue;
const next = curr.slice(0, j) + curr.slice(j + 1);
if (!visited.has(next)) {
visited.add(next);
queue.push(next);
}
}
}
if (found) break;
}
return res;
}
// Test
console.log(removeInvalidParenthesesBFS("()())()"));const visited = new Set([s]);Track visited strings to avoid duplicatesif (isValid(curr)) {Check if current string is valid parenthesesfor (let j = 0; j < curr.length; j++) {Generate next states by removing one parenthesisif (found) break;Stop BFS after first valid level to ensure minimal removalsO(n * 2^n)O(2^n)BFS explores all strings with increasing removals until valid strings found; each string generation is O(n).
BFS is often the best approach to guarantee minimal removals and avoid deep recursion, making it ideal for interviews.
| Approach | Time | Space | Stack Risk | Reconstruct | Use In Interview |
|---|---|---|---|---|---|
| 1. Brute Force | O(2^n * n) | O(n) recursion + O(2^n) results | Yes | Yes | Mention only - never code |
| 2. Backtracking with Pruning | O(2^n) worst, often better due to pruning | O(n) recursion + O(2^n) results | Possible | Yes | Good choice to code |
| 3. BFS Level-by-Level | O(n * 2^n) | O(2^n) | No | Yes | Optimal approach, good to mention or code |
How to Present
Step 1: Clarify input, output, and what makes a string valid.Step 2: Present brute force to show the exponential search space.Step 3: Introduce pruning with backtracking to reduce search space.Step 4: Explain BFS to guarantee minimal removals and avoid recursion depth.Step 5: Code the pruning or BFS approach depending on time.
Time Allocation
What the Interviewer Tests
Interviewer tests your understanding of parentheses validity, ability to prune search space, and knowledge of BFS vs DFS tradeoffs.
Common Follow-ups
- How to handle duplicates efficiently → Use sets or hash maps to avoid repeated states.
- Can you optimize space usage → Use iterative BFS with pruning or iterative DFS with memoization.
When to Use
1) Need to remove minimum invalid characters 2) Result must be all valid strings 3) Input is a string with parentheses 4) Problem involves exploring many combinations
