【LetMeFly】301.删除无效的括号:二进制枚举 / 回溯
力扣题目链接:https://leetcode.cn/problems/remove-invalid-parentheses/
给你一个由若干括号和字母组成的字符串 s ,删除最小数量的无效括号,使得输入的字符串有效。
返回所有可能的结果。答案可以按 任意顺序 返回。
示例 1:
输入:s = "()())()"
输出:["(())()","()()()"]
示例 2:
输入:s = "(a)())()"
输出:["(a())()","(a)()()"]
示例 3:
输入:s = ")("
输出:[""]
提示:
1 <= s.length <= 25s由小写英文字母以及括号'('和')'组成s中至多含20个括号
解题方法一:二进制枚举
首先遍历一遍原始字符串得到括号的下标有哪些、得到至少需要移除多少个括号。
关于至少需要移除多少个字符,可以参考昨天的题目《921.使括号有效的最少添加:一次遍历(贪心)》。
简言之就是使用一个变量
left记录左括号比右括号多几个,left < 0则说明需要移除当前右括号;以及最终剩下left个未配对的左括号也需要被移除。
假设括号有m个,那么我们可以使用一个m位的二进制数 i ∈ [ 0 , 2 m ) i\in [0, 2^m) i∈[0,2m)来表示保留哪个括号(其中i二进制下第j位为1的话表示整个字符串第j个括号被删除)。
假设要移除k个括号,如果i二进制下恰好有k个1,则构造对应的字符串,并遍历一遍看看该字符串是否合法。
关于一个括号序列字符串是否合法,类似问题"至少需要移除多少个字符使得括号序列合法",如果至少需要移除
0个括号则说明原始字符串合法。
时空复杂度
- 时间复杂度 O ( 2 m + n × C m k ) O(2^m+n\times C_m^k) O(2m+n×Cmk)
- 空间复杂度 O ( n × C m k ) O(n\times C_m^k) O(n×Cmk)
其中 n = l e n ( s ) n=len(s) n=len(s), m m m是字符串中的括号数量, k k k是至少需要移除括号的数量。
AC代码
C++
cpp
/*
* @LastEditTime: 2026-10-07 11:47:10
*/
class Solution {
private:
vector<int> idxs; // 括号位置
int mini_remove;
void getInfo(const string& s) {
int left = 0;
mini_remove = 0;
for (int i = 0, n = s.size(); i < n; i++) {
if (s[i] == '(') {
idxs.push_back(i);
left++;
} else if (s[i] == ')') {
idxs.push_back(i);
if (left) {
left--;
} else {
mini_remove++;
}
}
}
mini_remove += left;
}
string genS(const string& s, int mask) {
string this_s;
this_s.reserve(s.size() - mini_remove);
vector<int> deleted;
deleted.reserve(idxs.size());
for (int i = 0; i < idxs.size(); i++) {
if (mask >> i & 1) {
deleted.push_back(idxs[i]);
}
}
for (int is = 0, ic = 0; is < s.size(); is++) {
if (ic < deleted.size() && is == deleted[ic]) {
ic++;
} else {
this_s.push_back(s[is]);
}
}
return this_s;
}
bool ok(const string& s) {
int left = 0;
for (int i = 0, n = s.size(); i < n; i++) {
if (s[i] == '(') {
left++;
} else if (s[i] == ')') {
if (left) {
left--;
} else {
return false;
}
}
}
return !left;
}
public:
vector<string> removeInvalidParentheses(const string& s) {
unordered_set<string> se;
getInfo(s);
int parentheses = idxs.size();
for (int i = 0, to = 1 << parentheses; i < to; i++) {
if (__builtin_popcount(i) != mini_remove) {
continue;
}
string this_s = genS(s, i);
if (ok(this_s)) {
se.insert(this_s);
}
}
return vector<string>(se.begin(), se.end());
}
};
解题方法二:回溯
类似方法一,遍历一次原始字符串得到需要移除多少左括号、需要移除多少右括号。
写一个回溯函数dfs(s, idx, left, left_removed, right_removed),其中:
s是原始字符串idx是当前遍历到的下标left是当前已经保留的左括号比右括号多几个left_removed是当前已经移除的左括号数量right_removed是当前已经移除的右括号数量
如果idx == s.size(),则说明已经遍历完了原始字符串,终止递归。递归终止之前看下如果left为零、left_removed和right_removed分别等于需要移除的左右括号数量,则说明找到了一种可行的移除方案,放入答案集合中。
否则开始回溯尝试:
- 如果当前字符是左括号
(,并且还有左括号可以移除(left_removed < remove_left),则尝试移除当前左括号; - 如果当前字符是右括号
),并且还有右括号可以移除(right_removed < remove_right),则尝试移除当前右括号; - 尝试不移除当前字符(只要不是 "
left==0并且当前字符是右括号"就可以尝试保留当前字符)。递归结束回来记得弹出当前保留的字符。
时空复杂度
- 时间复杂度 O ( n × 2 m ) O(n \times 2^m) O(n×2m)
- 空间复杂度 O ( n × C m k ) O(n\times C_m^k) O(n×Cmk)
其中 n = l e n ( s ) n=len(s) n=len(s), m m m是字符串中的括号数量
AC代码
C++
cpp
/*
* @LastEditTime: 2026-10-07 14:28:52
*/
class Solution {
private:
int remove_left, remove_right;
string now;
unordered_set<string> ans;
void getInfo(const string& s) {
remove_left = remove_right = 0;
for (char c : s) {
if (c == '(') {
remove_left++;
} else if (c == ')') {
if (remove_left) {
remove_left--;
} else {
remove_right++;
}
}
}
}
void dfs(const string& s, int idx, int left, int left_removed, int right_removed) {
if (idx == s.size()) {
if (left == 0 && left_removed == remove_left && right_removed == remove_right) {
ans.insert(now);
}
return;
}
if (s[idx] == '(' && left_removed < remove_left) {
dfs(s, idx + 1, left, left_removed + 1, right_removed);
}
if (s[idx] == ')' && right_removed < remove_right) {
dfs(s, idx + 1, left, left_removed, right_removed + 1);
}
// don't remove
left += s[idx] == '(' ? 1 : s[idx] == ')' ? -1 : 0;
if (left < 0) {
return;
}
now.push_back(s[idx]);
dfs(s, idx + 1, left, left_removed, right_removed);
now.pop_back();
}
public:
vector<string> removeInvalidParentheses(const string& s) {
getInfo(s);
now.reserve(s.size() - remove_left - remove_right);
dfs(s, 0, 0, 0, 0);
return vector<string>(ans.begin(), ans.end());
}
};
End
今日LeetCode.每日一题连续1906天了。
同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~
千篇源码题解已开源