最长合法括号子序列
题目描述
一个合法的括号序列满足以下条件:
- 序列
()
被认为是合法的。 - 如果序列
X
与Y
是合法的,则XY
也被认为是合法的。 - 如果序列
X
是合法的,则(X)
也是合法的。
例如,()
,()()
,(())
这些都是合法的。
现在,给定一个由 (
和 )
组成的字符串。
请你求出其中的最长合法括号子序列的长度。
注意,子序列不一定连续。
输入格式
共一行,一个由 (
和 )
组成的字符串。
输出格式
一个整数,表示最长合法括号子序列的长度。
数据范围
前五个测试点满足, 1≤输入字符串的长度≤101≤输入字符串的长度≤10。
所有测试点满足,1≤输入字符串的长度≤1061≤输入字符串的长度≤106。
输入样例1:
(()))(
输出样例1:
4
输入样例2:
()()(()(((
输出样例2:
6
思路分析
一个括号序列是合法的括号序列,等价于(结论)
- 左右括号数量相等(扫描完)
- 所有前缀中,左括号数量 >= 右括号数量(枚举过程中)
只要上面两个性质成立,就构成合法的括号序列
所以我们在遍历的过程中,只需要维护一个变量cnt
,
- 当遇到左括号的时候,
cnt++
- 当遇到右括号的时候,
cnt--
上面两个条件则等价为cnt == 0
和cnt >= 0
接下来,我们分析如何得到最长的括号序列,序列最长,等价于右括号的数量最多,所以,我们采用贪心的思路,每当碰到右括号的时候,能选则选(即cnt>0的时候,碰到右括号一定选)。
即:考虑选右括号,对每个右括号可以选或者不选,我们的策略是从左向右对每个右括号采用贪心的思想选取
贪心的证明
代码实现
#include<iostream>
using namespace std;
int main()
{
string str;
cin >> str;
//cnt是题解中用来维护合法括号序列的变量
//r用来记录一共有多少右括号被加入合法括号序列
int cnt = 0, r = 0;
for(auto c : str)
{
if(c == '(') cnt++;
else if(cnt > 0)
{
cnt--;
r++;
}
}
cout << r * 2 << endl;
return 0;
}
总结
其实这道题目的本质就是使用变量cnt
维护已经遍历过的字符串,记录着能够与右括号匹配的左括号数量,cnt>0
代表着还能接受右括号,选择右括号的策略是贪心,能选就选。
为什么是右括号,原因在于我们从左向右遍历,右括号进来之后不会对它的右边(之后的遍历)产生影响,而它正好消耗左括号
另一种更加直观的想法,使用一个栈来维护遍历过的左括号
#include <iostream>
#include <stack>
using namespace std;
string str;
stack<char> stk;
int main() {
cin >> str;
int r = 0;
//栈stk中存放的是待处理的左括号
for (auto c : str) {
if (c == '(') stk.push(c);//入栈等价于cnt++
else
{
if(stk.size() > 0) //等价于cnt > 0
{
stk.pop();//等价于cnt--
r++;
}
}
}
cout << r * 2;
return 0;
}