题目
标题和出处
标题:平衡括号字符串的最少插入次数
难度
4 级
题目描述
要求
给你一个括号字符串 s \texttt{s} s,它只包含字符 ‘(’ \texttt{`('} ‘(’ 和 ‘)’ \texttt{`)'} ‘)’。一个括号字符串被称为平衡的当它满足:
- 任何左括号 ‘(’ \texttt{`('} ‘(’ 必须对应两个连续的右括号 ‘))’ \texttt{`))'} ‘))’。
- 左括号 ‘(’ \texttt{`('} ‘(’ 必须在对应的连续两个右括号 ‘))’ \texttt{`))'} ‘))’ 之前。
例如, "())" \texttt{"())"} "())", "())(())))" \texttt{"())(())))"} "())(())))" 和 "(())())))" \texttt{"(())())))"} "(())())))" 都是平衡的, ")()" \texttt{")()"} ")()", "()))" \texttt{"()))"} "()))" 和 "(()))" \texttt{"(()))"} "(()))" 都是不平衡的。
你可以在任意位置插入字符 ‘(’ \texttt{`('} ‘(’ 和 ‘)’ \texttt{`)'} ‘)’ 使字符串平衡。
请你返回让 s \texttt{s} s 平衡的最少插入次数。
示例
示例 1:
输入:
s
=
"(()))"
\texttt{s = "(()))"}
s = "(()))"
输出:
1
\texttt{1}
1
解释:第二个
‘(’
\texttt{`('}
‘(’ 有与之匹配的两个
‘))’
\texttt{`))'}
‘))’,但是第一个
‘(’
\texttt{`('}
‘(’ 只有一个
‘)’
\texttt{`)'}
‘)’。我们需要在字符串结尾额外增加一个
‘)’
\texttt{`)'}
‘)’ 使字符串变成平衡字符串
"(())))"
\texttt{"(())))"}
"(())))"。
示例 2:
输入:
s
=
"())"
\texttt{s = "())"}
s = "())"
输出:
0
\texttt{0}
0
解释:字符串已经平衡了。
示例 3:
输入:
s
=
"))())("
\texttt{s = "))())("}
s = "))())("
输出:
3
\texttt{3}
3
解释:添加
‘(’
\texttt{`('}
‘(’ 去匹配最开头的
‘))’
\texttt{`))'}
‘))’,然后添加
‘))’
\texttt{`))'}
‘))’ 去匹配最后一个
‘(’
\texttt{`('}
‘(’。
示例 4:
输入:
s
=
"(((((("
\texttt{s = "(((((("}
s = "(((((("
输出:
12
\texttt{12}
12
解释:添加
12
\texttt{12}
12 个
‘)’
\texttt{`)'}
‘)’ 得到平衡字符串。
示例 5:
输入:
s
=
")))))))"
\texttt{s = ")))))))"}
s = ")))))))"
输出:
5
\texttt{5}
5
解释:在字符串开头添加
4
\texttt{4}
4 个
‘(’
\texttt{`('}
‘(’ 并在结尾添加
1
\texttt{1}
1 个
‘)’
\texttt{`)'}
‘)’,字符串变成平衡字符串
"(((())))))))"
\texttt{"(((())))))))"}
"(((())))))))"。
数据范围
- 1 ≤ s.length ≤ 10 5 \texttt{1} \le \texttt{s.length} \le \texttt{10}^\texttt{5} 1≤s.length≤105
- s \texttt{s} s 只包含 ‘(’ \texttt{`('} ‘(’ 和 ‘)’ \texttt{`)'} ‘)’
前言
这道题和「使括号有效的最少添加」相似,都是需要插入最少的括号使得括号字符串平衡。区别在于,这道题中的每个左括号和两个右括号匹配。和「使括号有效的最少添加」一样,这道题也可以使用栈和计数的解法,具体做法有所不同。
解法一
思路和算法
平衡括号字符串中,每个左括号的右边都有两个连续的右括号和该左括号匹配。需要插入括号的情况有以下三种:
-
如果右括号左边没有左括号,则需要插入左括号;
-
如果左括号右边没有右括号,则需要插入右括号;
-
如果连续的右括号数量是奇数,则需要插入右括号使连续的右括号数量变成偶数,才可能满足每两个连续的右括号和一个左括号匹配。
可以使用栈存储左括号,从左到右遍历字符串 s s s,计算使字符串 s s s 平衡的最少插入次数,遍历过程中需要记录下标,对于当前下标,进行如下操作:
-
遇到左括号,则将左括号入栈,并将下标加 1 1 1;
-
遇到右括号,则需要使用两个连续的右括号和一个左括号匹配,依次进行如下操作:
-
如果栈不为空,则将栈顶的左括号出栈,表示该左括号被匹配,否则将插入次数加 1 1 1,表示需要插入一个左括号,然后使用插入的左括号匹配;
-
如果当前字符的后面一个字符是右括号(注意当前字符已经是右括号),则将下标加 2 2 2,表示使用连续两个右括号和左括号匹配,否则将插入次数加 1 1 1,并将下标加 1 1 1,表示需要插入一个右括号,然后使用连续两个右括号和左括号匹配。
-
遍历结束之后,如果栈不为空,则栈内的每个左括号都没有右括号匹配,对于栈内的每个左括号都需要插入两个右括号才能匹配,因此将插入次数加上栈内左括号的个数的 2 2 2 倍。
最终得到的插入次数即为使字符串 s s s 平衡的最少插入次数。
注意上述操作中,只计算需要插入的左括号个数和右括号个数,没有考虑插入括号的位置,这是因为每一次都可以在字符串的任意位置插入一个括号,无论是插入左括号还是右括号,都一定能找到合适的插入位置。
代码
class Solution {
public int minInsertions(String s) {
int insertions = 0;
Deque<Character> stack = new ArrayDeque<Character>();
int length = s.length();
int index = 0;
while (index < length) {
char c = s.charAt(index);
if (c == '(') {
stack.push(c);
index++;
} else {
if (!stack.isEmpty()) {
stack.pop();
} else {
insertions++;
}
if (index < length - 1 && s.charAt(index + 1) == ')') {
index += 2;
} else {
insertions++;
index++;
}
}
}
insertions += stack.size() * 2;
return insertions;
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。需要遍历字符串 s s s 一次,每次入栈和出栈操作的时间都是 O ( 1 ) O(1) O(1)。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。空间复杂度主要取决于栈空间,栈内的元素个数为 O ( n ) O(n) O(n)。
解法二
思路和算法
也可以不用栈,而是使用计数的方式计算使字符串 s s s 平衡的最少插入次数。
使用 insertions \textit{insertions} insertions 记录添加次数,使用 count \textit{count} count 记录未匹配的左括号的数量,初始时 insertions = count = 0 \textit{insertions} = \textit{count} = 0 insertions=count=0。
从左到右遍历字符串 s s s,遍历过程中记录下标,对于当前下标,进行如下操作:
-
遇到左括号,则将 count \textit{count} count 加 1 1 1,并将下标加 1 1 1;
-
遇到右括号,则需要使用两个连续的右括号和一个左括号匹配,依次进行如下操作:
-
如果 count > 0 \textit{count} > 0 count>0,则将 count \textit{count} count 减 1 1 1,表示一个左括号被匹配,否则将插入次数加 1 1 1,表示需要插入一个左括号,然后使用插入的左括号匹配;
-
如果当前字符的后面一个字符是右括号(注意当前字符已经是右括号),则将下标加 2 2 2,表示使用连续两个右括号和左括号匹配,否则将插入次数加 1 1 1,并将下标加 1 1 1,表示需要插入一个右括号,然后使用连续两个右括号和左括号匹配。
-
遍历结束之后, count \textit{count} count 为尚未匹配的左括号的数量,对于每个尚未匹配的左括号都需要插入两个右括号,因此将 insertions \textit{insertions} insertions 加 count × 2 \textit{count} \times 2 count×2。
最终得到的 insertions \textit{insertions} insertions 即为使字符串 s s s 平衡的最少插入次数。
代码
class Solution {
public int minInsertions(String s) {
int insertions = 0;
int count = 0;
int length = s.length();
int index = 0;
while (index < length) {
char c = s.charAt(index);
if (c == '(') {
count++;
index++;
} else {
if (count > 0) {
count--;
} else {
insertions++;
}
if (index < length - 1 && s.charAt(index + 1) == ')') {
index += 2;
} else {
insertions++;
index++;
}
}
}
insertions += count * 2;
return insertions;
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。需要遍历字符串 s s s 一次,每次更新计数的时间都是 O ( 1 ) O(1) O(1)。
-
空间复杂度: O ( 1 ) O(1) O(1)。