给你一个只包含 '('
和 ')'
的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例3:
输入:s = ""
输出:0
提示:
s[i]
为 '('
或 ')'
动态规划,设 f[i]
表示 s[0..i]
中必须以 s[i]
结尾的最长有效括号子串的长度。分以下情况讨论:
f[0] = 0,只含有一个字符肯定不是有效括号子串
从左到右遍历 s
:
s[i] == '('
,f[i] = 0
,有效括号子串必定以 )
结尾s[i] == ')'
,因为 f[i - 1]
是以 s[i - 1]
结尾的最长有效括号子串的长度,所以如果 i - f[i - 1] - 1
位置上是 (
,就能与 s[i]
凑出一对有效括号,此时 f[i] = f[i - 1] + 2
。但是还有一种情况,比如 "()(())"
,在遍历到最后一个 )
时,通过上述找到的最长有效括号子串是 "(())"
,但是前面的 "()"
部分可以跟 "(())"
结合构成更长的合法括号子串,所以还应该加上 f[i - f[i - 1] - 2]
。遍历过程保留最大值就是结果
时间复杂度: O ( n ) O(n) O(n)
额外空间复杂度: O ( n ) O(n) O(n)
法一代码:
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.length();
if ( n < 2 ) return 0;
vector<int> f( n, 0 );
int pre, ret = 0;
for ( int i = 1; i < n; ++i ) {
if ( s[i] == '(' ) continue;
pre = i - f[i - 1] - 1;
if ( pre >= 0 && s[pre] == '(' )
f[i] = f[i - 1] + 2 + ( pre > 0 ? f[pre - 1] : 0 );
ret = max( ret, f[i] );
}
return ret;
}
};
/*
时间:0ms,击败:100.00%
内存:7.2MB,击败:78.30%
*/
法二:
使用栈模拟。
考虑到一个合法的括号序列有两个特征:
我们可以根据第二个特征,将括号序列划分成一个个小区间,每个区间以右括号结尾且其前面是合法的括号子串,比如 “(()))())” 可以划分成 “(()))” 和 “())” 两段,并且不会出现合法括号子串跨越多段的情况(感兴趣可自行证明)。这样的话,我们就可以使用栈来操作,栈中保存的是合法括号子串前一个位置,开始为-1。
从左往右遍历整个字符串,具体操作流程:
如果 s[i]
是 (
,则入栈 i
如果 s[i]
是 )
,则弹出栈顶元素(表示右括号匹配到左括号),继续判断:
s[i]
是当前合法括号子串的右端点,所以将 i
入栈i - stk.top()
就是当前右括号对应的合法括号子串长度时间复杂度: O ( n ) O(n) O(n)
额外空间复杂度: O ( n ) O(n) O(n)
法二代码:
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.length();
if ( n < 2 ) return 0;
stack<int> stk;
stk.push( -1 );
int ret = 0;
for ( int i = 0; s[i]; ++i ) {
if ( s[i] == '(' ) stk.push( i );
else {
stk.pop();
if ( stk.empty() ) stk.push( i );
else ret = max( ret, i - stk.top() );
}
}
return ret;
}
};
/*
时间:0ms,击败:100.00%
内存:7.2MB,击败:81.77%
*/
贪心,两次扫描。
把 (
当作 1, 把 )
当作 -1 ,一个合法的括号序列其前缀和的值肯定为零,并且任何时刻,一个合法的括号序列,其前缀和不可能小于零(法二中第二条特征)。
于是可以从前往后扫描,pre
表示合法括号子串的前一个位置,初始值为 -1
,变量 cnt
记录前缀和,如果是左括号,则cnt += 1
,否则的话,则 cnt -= 1
。若 cnt > 0
,说明缺少右括号,pre
不动;若 cnt < 0
,说明缺少左括号,说明 s[pre +1...i]
这个区间左括号数量小于右括号数量,非法,需要抛弃该区间,令 pre = i, cnt = 0
;若 cnt == 0
,说明当前是一个合法的括号子串,则更新最大答案。
注意:上面保证了:任何时刻左括号数量不小于右括号数量,所以 “)))(((” 这种情况是不会被当做合法括号序列的。
但是,从左往右遍历对于 “((())” 这种情况是无法解决的,可以从右往左遍历,然后将上述条件取反,即可处理所有情况。
时间复杂度: O ( n ) O(n) O(n)
额外空间复杂度: O ( 1 ) O(1) O(1)
法三代码:
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.length();
if ( n < 2 ) return 0;
int cnt = 0, pre = -1, ret = 0;
for ( int i = 0; s[i]; ++i ) {
if ( s[i] == '(' ) ++cnt;
else --cnt;
if ( cnt < 0 ) pre = i, cnt = 0;
else if ( !cnt ) ret = max( ret, i - pre );
}
pre = n, cnt = 0;
for ( int i = n - 1; i >= 0; --i ) {
if ( s[i] == ')' ) ++cnt;
else --cnt;
if ( cnt < 0 ) pre = i, cnt = 0;
else if ( !cnt ) ret = max( ret, pre - i );
}
return ret;
}
};
/*
时间:4ms,击败:91.21%
内存:6.6MB,击败:99.62%
*/