Missing undo step on backtrack
Wrong move: Mutable state leaks between branches.
Usually fails on: Later branches inherit selections from earlier branches.
Fix: Always revert state changes immediately after recursive call.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
Given a string s that contains parentheses and letters, remove the minimum number of invalid parentheses to make the input string valid.
Return a list of unique strings that are valid with the minimum number of removals. You may return the answer in any order.
Example 1:
Input: s = "()())()" Output: ["(())()","()()()"]
Example 2:
Input: s = "(a)())()" Output: ["(a())()","(a)()()"]
Example 3:
Input: s = ")("
Output: [""]
Constraints:
1 <= s.length <= 25s consists of lowercase English letters and parentheses '(' and ')'.20 parentheses in s.Problem summary: Given a string s that contains parentheses and letters, remove the minimum number of invalid parentheses to make the input string valid. Return a list of unique strings that are valid with the minimum number of removals. You may return the answer in any order.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Backtracking
"()())()"
"(a)())()"
")("valid-parentheses)minimum-number-of-swaps-to-make-the-string-balanced)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #301: Remove Invalid Parentheses
class Solution {
private String s;
private int n;
private Set<String> ans = new HashSet<>();
public List<String> removeInvalidParentheses(String s) {
this.s = s;
this.n = s.length();
int l = 0, r = 0;
for (char c : s.toCharArray()) {
if (c == '(') {
++l;
} else if (c == ')') {
if (l > 0) {
--l;
} else {
++r;
}
}
}
dfs(0, l, r, 0, 0, "");
return new ArrayList<>(ans);
}
private void dfs(int i, int l, int r, int lcnt, int rcnt, String t) {
if (i == n) {
if (l == 0 && r == 0) {
ans.add(t);
}
return;
}
if (n - i < l + r || lcnt < rcnt) {
return;
}
char c = s.charAt(i);
if (c == '(' && l > 0) {
dfs(i + 1, l - 1, r, lcnt, rcnt, t);
}
if (c == ')' && r > 0) {
dfs(i + 1, l, r - 1, lcnt, rcnt, t);
}
int x = c == '(' ? 1 : 0;
int y = c == ')' ? 1 : 0;
dfs(i + 1, l, r, lcnt + x, rcnt + y, t + c);
}
}
// Accepted solution for LeetCode #301: Remove Invalid Parentheses
func removeInvalidParentheses(s string) []string {
vis := map[string]bool{}
l, r, n := 0, 0, len(s)
for _, c := range s {
if c == '(' {
l++
} else if c == ')' {
if l > 0 {
l--
} else {
r++
}
}
}
var dfs func(i, l, r, lcnt, rcnt int, t string)
dfs = func(i, l, r, lcnt, rcnt int, t string) {
if i == n {
if l == 0 && r == 0 {
vis[t] = true
}
return
}
if n-i < l+r || lcnt < rcnt {
return
}
if s[i] == '(' && l > 0 {
dfs(i+1, l-1, r, lcnt, rcnt, t)
}
if s[i] == ')' && r > 0 {
dfs(i+1, l, r-1, lcnt, rcnt, t)
}
x, y := 0, 0
if s[i] == '(' {
x = 1
} else if s[i] == ')' {
y = 1
}
dfs(i+1, l, r, lcnt+x, rcnt+y, t+string(s[i]))
}
dfs(0, l, r, 0, 0, "")
ans := make([]string, 0, len(vis))
for v := range vis {
ans = append(ans, v)
}
return ans
}
# Accepted solution for LeetCode #301: Remove Invalid Parentheses
class Solution:
def removeInvalidParentheses(self, s: str) -> List[str]:
def dfs(i, l, r, lcnt, rcnt, t):
if i == n:
if l == 0 and r == 0:
ans.add(t)
return
if n - i < l + r or lcnt < rcnt:
return
if s[i] == '(' and l:
dfs(i + 1, l - 1, r, lcnt, rcnt, t)
elif s[i] == ')' and r:
dfs(i + 1, l, r - 1, lcnt, rcnt, t)
dfs(i + 1, l, r, lcnt + (s[i] == '('), rcnt + (s[i] == ')'), t + s[i])
l = r = 0
for c in s:
if c == '(':
l += 1
elif c == ')':
if l:
l -= 1
else:
r += 1
ans = set()
n = len(s)
dfs(0, l, r, 0, 0, '')
return list(ans)
// Accepted solution for LeetCode #301: Remove Invalid Parentheses
struct Solution;
use std::collections::HashSet;
impl Solution {
fn remove_invalid_parentheses(s: String) -> Vec<String> {
let mut cur = vec![];
let s: Vec<char> = s.chars().collect();
let n = s.len();
let mut min = std::usize::MAX;
let mut res: HashSet<String> = HashSet::new();
Self::dfs(0, 0, 0, &mut cur, &mut res, &mut min, &s, n);
res.into_iter().collect()
}
fn dfs(
start: usize,
left: usize,
remove: usize,
cur: &mut Vec<char>,
all: &mut HashSet<String>,
min: &mut usize,
s: &[char],
n: usize,
) {
if start == n {
if left != 0 {
return;
}
if remove > *min {
return;
}
if remove < *min {
*min = remove;
all.clear();
}
let s = cur.iter().copied().collect();
all.insert(s);
} else {
match s[start] {
'(' => {
cur.push('(');
Self::dfs(start + 1, left + 1, remove, cur, all, min, s, n);
cur.pop();
Self::dfs(start + 1, left, remove + 1, cur, all, min, s, n);
}
')' => {
if left > 0 {
cur.push(')');
Self::dfs(start + 1, left - 1, remove, cur, all, min, s, n);
cur.pop();
}
Self::dfs(start + 1, left, remove + 1, cur, all, min, s, n);
}
_ => {
cur.push(s[start]);
Self::dfs(start + 1, left, remove, cur, all, min, s, n);
cur.pop();
}
}
}
}
}
#[test]
fn test() {
let s = "()())()".to_string();
let mut res = vec_string!["()()()", "(())()"];
let mut ans = Solution::remove_invalid_parentheses(s);
ans.sort();
res.sort();
assert_eq!(ans, res);
let s = "(a)())()".to_string();
let mut res = vec_string!["(a)()()", "(a())()"];
let mut ans = Solution::remove_invalid_parentheses(s);
ans.sort();
res.sort();
assert_eq!(ans, res);
let s = ")(".to_string();
let mut res = vec_string![""];
let mut ans = Solution::remove_invalid_parentheses(s);
ans.sort();
res.sort();
assert_eq!(ans, res);
let s = ")(f".to_string();
let mut res = vec_string!["f"];
let mut ans = Solution::remove_invalid_parentheses(s);
ans.sort();
res.sort();
assert_eq!(ans, res);
}
// Accepted solution for LeetCode #301: Remove Invalid Parentheses
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #301: Remove Invalid Parentheses
// class Solution {
// private String s;
// private int n;
// private Set<String> ans = new HashSet<>();
//
// public List<String> removeInvalidParentheses(String s) {
// this.s = s;
// this.n = s.length();
// int l = 0, r = 0;
// for (char c : s.toCharArray()) {
// if (c == '(') {
// ++l;
// } else if (c == ')') {
// if (l > 0) {
// --l;
// } else {
// ++r;
// }
// }
// }
// dfs(0, l, r, 0, 0, "");
// return new ArrayList<>(ans);
// }
//
// private void dfs(int i, int l, int r, int lcnt, int rcnt, String t) {
// if (i == n) {
// if (l == 0 && r == 0) {
// ans.add(t);
// }
// return;
// }
// if (n - i < l + r || lcnt < rcnt) {
// return;
// }
// char c = s.charAt(i);
// if (c == '(' && l > 0) {
// dfs(i + 1, l - 1, r, lcnt, rcnt, t);
// }
// if (c == ')' && r > 0) {
// dfs(i + 1, l, r - 1, lcnt, rcnt, t);
// }
// int x = c == '(' ? 1 : 0;
// int y = c == ')' ? 1 : 0;
// dfs(i + 1, l, r, lcnt + x, rcnt + y, t + c);
// }
// }
Use this to step through a reusable interview workflow for this problem.
Generate every possible combination without any filtering. At each of n positions we choose from up to n options, giving nⁿ total candidates. Each candidate takes O(n) to validate. No pruning means we waste time on clearly invalid partial solutions.
Backtracking explores a decision tree, but prunes branches that violate constraints early. Worst case is still factorial or exponential, but pruning dramatically reduces the constant factor in practice. Space is the recursion depth (usually O(n) for n-level decisions).
Review these before coding to avoid predictable interview regressions.
Wrong move: Mutable state leaks between branches.
Usually fails on: Later branches inherit selections from earlier branches.
Fix: Always revert state changes immediately after recursive call.