32. 最长有效括号

32. 最长有效括号

题目描述

给你一个只包含 '('')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例1:

输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"

示例2:

输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"

示例3:

输入:s = ""
输出:0

提示:

  • 0 ≤ s . l e n g t h ≤ 3 ∗ 1 0 4 0 \le s.length \le 3 * 10^4 0s.length3104
  • s[i]'('')'

题解:
法一:

动态规划,设 f[i] 表示 s[0..i] 中必须以 s[i] 结尾的最长有效括号子串的长度。分以下情况讨论:

  • f[0] = 0,只含有一个字符肯定不是有效括号子串

  • 从左到右遍历 s

    1. 如果 s[i] == '('f[i] = 0 ,有效括号子串必定以 ) 结尾
    2. 如果 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. 左右括号数量相等
  2. 任意一个括号序列的前缀,左括号数量 >= 右括号数量

我们可以根据第二个特征,将括号序列划分成一个个小区间,每个区间以右括号结尾且其前面是合法的括号子串,比如 “(()))())” 可以划分成 “(()))” 和 “())” 两段,并且不会出现合法括号子串跨越多段的情况(感兴趣可自行证明)。这样的话,我们就可以使用栈来操作,栈中保存的是合法括号子串前一个位置,开始为-1。

从左往右遍历整个字符串,具体操作流程:

  • 如果 s[i]( ,则入栈 i

  • 如果 s[i]) ,则弹出栈顶元素(表示右括号匹配到左括号),继续判断:

    1. 如果栈为空,说明没有与之匹配的左括号,也就是说,s[i] 是当前合法括号子串的右端点,所以将 i 入栈
    2. 如果栈非空,那么 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%
*/
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值