阿犇

记录生活中的点点滴滴

0%

删除无效的括号

Trace

10.27 每日一题

传送门:https://leetcode-cn.com/problems/remove-invalid-parentheses/

Problem

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

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

方法一:dfs+剪枝

思路

我们发现,删除的无效括号的最小数量是可以提前求出来的。

可以用一个计数器$lnum$来模拟栈,往栈中不断地添加左括号;

之后出现的右括号都可以去栈中匹配左括号,最后栈中剩下的即为要删去的左括号,数量等于$lnum$。

右括号出现时,栈中没有左括号用来匹配,则右括号需要删去,计数器$rnum$++,记录需要删去的右括号数量。

$dfs$搜索

$dfs$所有删去$lnum$个左括号、$rnum$个右括号的序列,并判断是否有效。

剪枝

记录当前序列中的左括号数目$lc$与右括号数目$rc$,如果右括号数目$rc>lc$,则这个序列一定为无效序列,停止搜索。

去重

按照上述算法搜索出的结果中可能含有重复序列,比如当前遇到的字符串为” (((())”,去掉前四个左括号中的任意一个,生成的字符串是一样的,均为 “(())”。

  • 在最后的结果中去重,可以使用哈希表/$set$/$sort+unique$。

  • 在搜索过程中,遇到相邻元素相同的情况时,只需要搜索一次就好。(剪枝)

程序

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
81
82
83
84
85
86
class Solution {
public:
vector<string> ans;

bool isValid(string s){
stack<char> st;
for(int i=0;i<s.size();i++){
if(s[i]=='('){
st.push(s[i]);
}
else if(s[i]==')'){
if(st.empty()){
return false;
}
else{
if(st.top()!='('){
return false;
}
else{
st.pop();
}
}
}
}
return true;
}

void dfs(string s,int lremove,int rremove,int start,int lc,int rc){
if(lremove==0&&rremove==0){
if(isValid(s)){
ans.push_back(s);
}
return;
}
for(int i=start;i<s.size();i++){
if(i!=start && s[i]==s[i-1]){
if(s[i]=='(') lc++;
if(s[i]==')') rc++;
continue;
}

// 去掉一个左括号
if(lremove>0&&s[i]=='('){
dfs(s.substr(0,i)+s.substr(i+1),lremove-1,rremove,i,lc,rc);
}
//去掉一个右括号
if(rremove>0&&s[i]==')'){
dfs(s.substr(0,i)+s.substr(i+1),lremove,rremove-1,i,lc,rc);
}
if(s[i]=='('){
lc++;
}
if(s[i]==')'){
rc++;
}
if(rc>lc){
break;
}
}

}

vector<string> removeInvalidParentheses(string s) {
// 先计算需要删除的左括号数量、右括号数量
int lnum=0;
int rnum=0;
for(int i=0;i<s.size();i++){
if(s[i]=='('){
lnum++;
}
else if(s[i]==')'){
if(lnum>0){
lnum--;
}
else{
rnum++;
}
}
}

dfs(s,lnum,rnum,0,0,0);
// sort(ans.begin(),ans.end());
// ans.erase(unique(ans.begin(),ans.end()),ans.end());
return ans;
}
};

时间复杂度:$O(n*2^n)$,$n$为字符串的长度,长度为$n$的字符串最多有$2^n$个子串,需要对每个子串进行一次合法性判断。

方法二:bfs

思路

注意到题目说的是删去最少的括号,我们可以每次删去一个括号,就所有删去一个括号的操作作为$bfs$的一层,判断这一层有没有符合条件的序列;如果没有就继续删一个括号,转向下一层,以此类推,一旦在某一层找到答案便停止搜索。

我们做$bfs$,上一层$level$和下一层$level$之间的关系为:把所有上一层$level$中的每个元素都拿出来,列举出在删除一个括号后的所有可能的情况。(不管删除以后是否合法),添加到下一个$level$中的元素。

例如:$current level$是 ["(()", "())"]

1
2
3
4
- 那么下一层level中的元素应该是:

1. 对 "(()" 删除一个括号的所有可能为: (), (), ((
2. 对 "())" 删除一个括号的所有可能为: (), )), ()

注意到有重复的序列,可以使用$unordered_set$代替$queue$,以达到去重的目的。

程序

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
class Solution {
public:
bool isvalid(string s){
int ans=0;
for(int i=0;i<s.size();i++){
if(s[i]=='('){
ans++;
}
else if(s[i]==')'){
ans--;
if(ans<0){
return false;
}
}
}
if(ans>0) return false;
return true;
}
// bfs
vector<string> removeInvalidParentheses(string s) {
// unordered_set模拟队列
// 每一层删去一些字符后会有重复元素,可以在进队前去重
unordered_set<string> q;
// 存答案
vector<string> ans;
q.insert(s);
while(!q.empty()){
// 遍历这一层中的所有序列,如果有符合条件的,加入ans中,并且终止向下一层搜索。
for(auto ss:q){
if(isvalid(ss)){
ans.push_back(ss);
}
}
if(ans.size()){
return ans;
}
// 继续搜索下一层
unordered_set<string> curq;
for(auto ss:q){
for(int i=0;i<ss.size();i++){
if(i>0&&ss[i]==ss[i-1]){
continue;
}
if(ss[i]=='('||ss[i]==')'){
curq.insert(ss.substr(0,i)+ss.substr(i+1));
}
}
}
q=curq;
}
return ans;
}
};

时间复杂度:$O(n*2^n)$,$n$为字符串的长度,长度为$n$的字符串最多有$2^n$个子串,需要对每个子串进行一次合法性判断。

您的支持是我继续创作的最大动力!