301.删除无效的括号:二进制枚举 / 回溯

【LetMeFly】301.删除无效的括号:二进制枚举 / 回溯

力扣题目链接:https://leetcode.cn/problems/remove-invalid-parentheses/

给你一个由若干括号和字母组成的字符串 s ,删除最小数量的无效括号,使得输入的字符串有效。

返回所有可能的结果。答案可以按 任意顺序 返回。

 

示例 1:

输入:s = "()())()"
输出:["(())()","()()()"]

示例 2:

输入:s = "(a)())()"
输出:["(a())()","(a)()()"]

示例 3:

输入:s = ")("
输出:[""]

 

提示:

  • 1 <= s.length <= 25
  • s 由小写英文字母以及括号 '(' 和 ')' 组成
  • s 中至多含 20 个括号

解题方法一:二进制枚举

首先遍历一遍原始字符串得到括号的下标有哪些、得到至少需要移除多少个括号。

关于至少需要移除多少个字符,可以参考昨天的题目《921.使括号有效的最少添加:一次遍历(贪心)》。

简言之就是使用一个变量left记录左括号比右括号多几个,left < 0则说明需要移除当前右括号;以及最终剩下left个未配对的左括号也需要被移除。

假设括号有m个,那么我们可以使用一个m位的二进制数$i\in [0, 2^m)$来表示保留哪个括号(其中i二进制下第j位为1的话表示整个字符串第j个括号被删除)。

假设要移除k个括号,如果i二进制下恰好有k个1,则构造对应的字符串,并遍历一遍看看该字符串是否合法。

关于一个括号序列字符串是否合法,类似问题“至少需要移除多少个字符使得括号序列合法”,如果至少需要移除0个括号则说明原始字符串合法。

时空复杂度

  • 时间复杂度$O(2^m+n\times C_m^k)$
  • 空间复杂度$O(n\times C_m^k)$

其中$n=len(s)$,$m$是字符串中的括号数量,$k$是至少需要移除括号的数量。

AC代码

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/*
* @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分别等于需要移除的左右括号数量,则说明找到了一种可行的移除方案,放入答案集合中。

否则开始回溯尝试:

  1. 如果当前字符是左括号(,并且还有左括号可以移除(left_removed < remove_left),则尝试移除当前左括号;
  2. 如果当前字符是右括号),并且还有右括号可以移除(right_removed < remove_right),则尝试移除当前右括号;
  3. 尝试不移除当前字符(只要不是 “left==0并且当前字符是右括号”就可以尝试保留当前字符)。递归结束回来记得弹出当前保留的字符。

时空复杂度

  • 时间复杂度$O(n \times 2^m)$
  • 空间复杂度$O(n\times C_m^k)$

其中$n=len(s)$,$m$是字符串中的括号数量

AC代码

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*
* @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和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

千篇源码题解已开源


301.删除无效的括号:二进制枚举 / 回溯
https://blog.letmefly.xyz/2026/10/07/LeetCode 0301.删除无效的括号/
作者
发布于
2026年10月7日
许可协议