LeetCode 32.最长有效括号

题目概述:

在这里插入图片描述
题目链接:点我做题

题解

一、过不了的暴力算法

  说实在的,我第一个想到的是这个方法,但是这个方法会超出时间限制。
  它的思路很简单,两层循环找到每个子串,然后利用栈判断每个子串是否为有效子串,方法比leetcode那道有效括号的题还简单,因为这个是只有’(‘和’)'的。
代码:

class Solution {
public:
    int longestValidParentheses(string s) 
    {
        if (s == "")
        {
            return 0;
        }
        int len = s.size();
        int maxsize = 0;
        for (int left = 0; left < len; left++)
        {
            for (int right = left + 1; right < len; right++)
            {
                int size = right - left + 1;
                if (maxsize < size && isValidParentheses(s, left, right))
                {
                    maxsize = size;
                }
            }
        }
        return maxsize;
    }
    bool isValidParentheses(const string& s, int left, int right)
    {
        stack<char> st;
        int cur = left;
        for (cur = left; cur <= right; cur++)
        {
            char tmp = s[cur];
            if (tmp == '(')
            {
                st.push(tmp);
            }
            else if (tmp == ')')
            {
                if (st.empty())
                {
                    return false;
                }
                else
                {
                    st.pop();
                }
            }
        }
        if (st.empty())
        {
            return true;
        }
        else
        {
            return false;
        }
    }
};

时间复杂度: O ( n 3 ) O(n^3) O(n3)
空间复杂度: O ( n ) O(n) O(n)

二、动态规划

  这样看起来上一个位置和前一个位置有关系的题貌似都可以用动态规划想一想(本小白是这么想的),定义 d p [ i ] dp[i] dp[i]表示以 s [ i ] s[i] s[i]为结尾的最长有效子串的长度,我们的目的是通过一趟遍历把每个位置的 d p [ i ] dp[i] dp[i]都求出来同时每轮取大,返回最大值.
  首先,有效子串的结尾不可能是’(’,所以可以在创建dp数组时先把所有的 d p [ i ] dp[i] dp[i]都置0,然后只处理 s [ i ] = = ′ ) ′ s[i] == ')' s[i]==)的位置。
  首先检查它的前继 s [ i − 1 ] s[i-1] s[i1]是什么:

  1. 如果它的前继 s [ i − 1 ] s[i - 1] s[i1]是’(’
      那么首先它会和前继匹配形成一对有效括号,然后再看它的前继的前一个元素 s [ i − 2 ] s[i - 2] s[i2],看 i − 2 i - 2 i2是否越界?
    1.1 如果 i − 2 i-2 i2没越界,我们求 d p [ i ] dp[i] dp[i]的时候 d p [ i − 2 ] dp[i - 2] dp[i2]一定已经求过了, d p [ i − 2 ] dp[i - 2] dp[i2]的含义是以 i − 2 i-2 i2位置为结尾的最长有效子串长度,显然 i i i位置的最长有效子串长度就等于2(它和 s [ i − 1 ] s[i-1] s[i1]匹配形成的有效子串)+以 i − 2 i-2 i2位置为结尾的最长有效子串长度;
    1.2 如果 i − 2 i-2 i2越界了,那前面没有最长有效子串了给 [ i − 1 , i ] [i - 1, i] [i1,i]链接了, d p [ i ] dp[i] dp[i]直接就等于2就好.
  2. 如果它的前继 s [ i − 1 ] s[i -1] s[i1]是’)’
      首先这种情况我们要往前找,首先 d p [ i − 1 ] dp[i - 1] dp[i1]已经求出,它代表以 i − 1 i-1 i1位置为结尾最长子串长度是 d p [ i − 1 ] dp[i - 1] dp[i1],我们不妨先把这个最长子串连接到我们的串前面,这样就把 s [ i − 1 ] s[i-1] s[i1]的’)‘就匹配掉了。
      然后往前走,找这个子串起始位置前一个位置 i − 1 − d p [ i − 1 ] i - 1 - dp[i - 1] i1dp[i1](可以自己画图走走),检查一下这个位置是否越界:
    2.1 如果越界了,那么我们就找不到’('去跟s[i]匹配了,这种情况就不要处理 d p [ i ] dp[i] dp[i],直接维持原来的值0就行,表明当前位置没有有效子串
    2.2 如果没越界,那么检查 d p [ i − 1 − d p [ i − 1 ] ] = = ′ ( ′ dp[i - 1 - dp[i - 1]]== '(' dp[i1dp[i1]]==(是否成立
    2.2.1 如果成立,那就找到了s[i]应该匹配的左括号,仿照上面,在检查一下这个位置前一个位置是否越界,不越界的话就再连接上前面的最长相等子串,越界的话长度就只有 d p [ i − 1 ] dp[i - 1] dp[i1](前面连接的 i − 1 i-1 i1位置的最长相等子串)和2(这个位置和i位置匹配形成的有效括号)
    $
    dp[i] = dp[i - 1] + 2 + (i - 2 - dp[i - 1] >= 0 ? dp[i - 2 - dp[i - 1]]:0);
    $
    2.2.3 如果不成立,那么说明没找到与 i i i位置相匹配的左括号,那么前面还可能有能把这两个左括号都消掉的串吗,很显然不可能有,因为我们 d p [ i − 1 ] dp[i - 1] dp[i1]找的就是以 i − 1 i-1 i1为结尾的最长相等子串,如果前面还有能消掉 i − 2 − d p [ i − 1 ] i - 2 -dp[i -1] i2dp[i1]位置的左括号,那和我们的 d p [ i − 1 ] dp[i - 1] dp[i1]的值就矛盾了,所以这种情况下 i i i位置没有有效子串, d p [ i ] dp[i] dp[i]不做处理就好.
    代码:
class Solution {
public:
    int longestValidParentheses(string s) 
    {
        //动态规划法
        //一维数组dp来表示以当前字符结尾的最长有效括号长度
        //显然所有以'('结尾的位置j,dp[j]都等于0,
        //因为有效括号结尾不可能是'('
        //然后考虑以')'结尾的位置i,先判断s[i-1]
        //1如果s[i-1]=='(' 
        //那么这个以i位置结尾的最长有效括号长度dp[i]
        //就等于dp[i] =  2 + (i - 2 >= 0 ? dp[i-2] : 0)
        //相当于s[i - 1]和s[i]配对后连上前面的有效括号
        //如果前面越界了就是0
        //2如果s[i-1]==')' 
        //那么先连上前一个位置的最长有效括号长度dp[i - 1]
        //这样就抵消掉了s[i-1]的')'
        //然后往前走 i-1到达前一个位置 i-1-dp[i-1]
        //到达以s[i - 1]为结尾的有效括号串的最左边的前一个位置
        //然后看看这个位置是否是'(' 
        //2.1如果是 那这个位置就抵消掉了s[i]的')'
        //然后检查一下这个位置的前一个位置是否越界 i - 2 - dp[i - 1] >= 0
        //2.1.1如果没越界 再连上这个以这个位置为结尾的有效括号
        //即dp[i] = dp[i - 1] + dp[i - 2 -dp[i - 1]] + 2
        //2.1.2如果越界了 那么dp[i]相对于dp[i - 1]
        //就只增加了2 s[i]的')'和 s[i - 1 - dp[i-1]]的')'
        //dp[i] = dp[i - 1] + 2
        //2.2如果不是 那说明匹配失败了 
        //以s[i]位置为结尾的最长子串的长度就应该是0
        if (s == "")
        {
            return 0;
        }
        int maxlen = 0;
        int len = s.size();
        vector<int> dp(len, 0);
        for (int i = 0; i < len; i++)
        {
            //s[i]=='('的不用管 直接过去就行
            if (s[i] == ')')
            {
                if (i - 1 >= 0)
                {
                    //i -1 小于0的话 说明第一个字符是')'
                    //一样dp[0]等于0就行 else情况放着不管就好
                    if (s[i - 1] == '(')
                    {
                        dp[i] = 2 + (i - 2 >= 0 ? dp[i - 2]: 0);
                    }
                    else if (s[i - 1] == ')')
                    {
                        if (i - 1 - dp[i - 1] >= 0 
                        && s[i - 1 - dp[i - 1]] == '(')
                        {
                            //同样越界的话或者不等于')'
                            //就就找不到给s[i]匹配的'('
                            //自然dp[i] = 0
                            //else情况放着不用管
                            dp[i] = dp[i - 1] 
                            + (i - 2 - dp[i - 1] >= 0 ?
                             dp[i - 2 - dp[i - 1]] : 0) + 2;
                            //若没越界 连上
                            //若越界了就不管了
                        }
                    }
                }
            }
            if (dp[i] > maxlen)
            {
                maxlen = dp[i];
            }
        }
        return maxlen;
    }
};

时间复杂度: O ( n ) O(n) O(n)(只遍历了一遍串)
空间复杂度: O ( n ) O(n) O(n)( d p dp dp数组的空间消耗)

三、利用栈来解决

  这个方法利用了有效子串左括号入栈,右括号出栈,一定可以把栈清空,那么我们如果我们能在栈里头放上有效子串的起始位置或者有效子串的起始位置的前一个位置,那么就可以得到这一轮的有效子串的长度。
  所以我们把当前时刻最后没有得到匹配的右括号的下标入栈,然后遇到左括号入栈,遇到右括号出栈,那么 下 一 轮 有 效 括 号 子 串 的 结 尾 下 标 − 上 一 个 没 有 被 匹 配 的 ′ ) ′ 的 下 标 = c u r s i z e 下一轮有效括号子串的结尾下标 - 上一个没有被匹配的')'的下标 = cursize )=cursize,这个上一个没有被匹配的’)‘的下标就是这个有效子串的开头的’('的前一个位置。
  为控制边界情况,我们先压一个-1进去,遇到左括号入栈,遇到右括号pop()
  如果pop()后栈为空了,那么说明你是最新的没有得到匹配的右括号,把你的下标入栈;
  如果pop()后栈不为空,那么说明栈内剩的要么是上一个没有得到匹配的右括号,也可能是上一个没有得到匹配的左括号,总之这时有效括号长度等于当前位置下标减去上一个没有得到匹配的括号的下标,即 c u r s i z e = i − s t . t o p ( ) ; cursize = i - st.top(); cursize=ist.top();,然后一轮轮取大。
代码:

class Solution {
public:
    int longestValidParentheses(string s) 
    {
        int size = s.size();
        if (size == 0)
        {
            return 0;
        }
        //栈做法 我们知道 
        //正确的有效括号子串通过出栈入栈操作一定可以把栈清空
        //那么如果里头先放一个上一个没有被匹配的')'的下标
        //下一轮有效括号子串的结尾下标 - 上一个没有被匹配的')'的下标 
        //= cursize
        //其实这个上一个没有被匹配的')'的下标就是
        //这个有效子串的开头的'('的前一个位置
        //这个cursize长度就是这轮有效子串的长度
        //然后每轮取大就行了
        //为了保证边界条件 先给栈压一个-1进去
        stack<int> st;
        st.push(-1);
        int maxsize = 0;
        for (int i = 0; i < size; i++)
        {
            if (s[i] == '(')
            {
                st.push(i);
            }
            else
            {
                //右括号入栈先pop一次
                st.pop();
                if (st.empty())
                {
                    //如果pop完了栈是空的 
                    //那说明上个没有匹配完的右括号被我们pop掉了
                    //因为一开始栈不是空的嘛 有个-1
                    //那它就要更新成新的没有匹配完的右括号
                    //它的下标入栈
                    st.push(i);
                }
                else
                {
                    //如果pop完了栈不为空
                    //说明栈中那个就是上一次没有匹配完成的右括号下标
                    //或者是上一个匹配的左括号的下标
                    //这两种情况我们都计算一下当前最长有效长度
                    int cursize = i - st.top();
                    if (cursize > maxsize)
                    {
                        maxsize = cursize;
                    }
                    
                }
            }
        }
        return maxsize;
    }
};

时间复杂度: O ( n ) O(n) O(n)(只遍历了一遍串)
空间复杂度: O ( n ) O(n) O(n)(栈的消耗)

四、利用左括号右括号计数器来解决

  这个方法的步骤是先从左往右变量一遍串,如果遇到’(’,则计数器 l e f t + + left++ left++;如果遇到’)’,则计数器 r i g h t + + right++ right++然后检查right是否大于left,如果大于说明此时我们的串右括号多了,我们不可能通过往前走获得左括号匹配这个右括号,已经不是有效子串了,把 r i g h t , l e f t right,left right,left恢复为0;否则检查right是否等于left,如果相等说明此时有了一个有效子串,火速将它去过往的最大相等子串比较取大。
  这么一遍遍历会漏掉 l e f t > r i g h t left>right left>right同样可以出现的有效子串,所以我们再通过从尾到头遍历来弥补这一点
  先把 l e f t , r i g h t left,right left,right置0,然后从串尾往前走,如果遇到’(’,则计数器 l e f t + + left++ left++;如果遇到’)’,则计数器 r i g h t + + right++ right++然后检查left是否大于right,如果大于说明反向走的过程中左括号多了,我们不可能通过往前走获得右括号匹配这个左括号,所以已经不是有效子串了,把 l e f t , r i g h t left,right left,right恢复为0;否则检查right是否等于left,如果相等说明此时有了一个有效子串,火速将它去过往的最大相等子串比较取大。
  从后往前走的过程会补上 l e f t > r i g h t left>right left>right可能出现的相等子串,因为left还没大于right就相等了,然后就判断为相等子串了,left一大于right,立马找下一组有效串,不过反向遍历会忽略 r i g h t > l e f t right > left right>left可能出现的有效子串,同理,被上面的正向走弥补了。
  这个方法最大的优势在于其空间复杂度为 O ( 1 ) O(1) O(1),时间复杂度为 O ( n ) O(n) O(n),可以说是此问题综合最优的算法了.

class Solution {
public:
    int longestValidParentheses(string s) 
    {
        //双计数器法 left right
        //这个方法的巧妙在于它精妙的控制了“有效括号”
        //当从左往右遍历是 如果是'(' 则left++
        //如果是')' 则right++
        //然后检查 如果right > left
        //说明这个是无效括号了 我们就把right 和left都重置回0
        //如果left == right
        //说明这个是一组有效括号 更新最长长度为2left
        //这样的问题在于会忽略(()的()括号 
        //会忽略left > right时也可以出现的有效括号
        //没关系啊 再从末尾到0反向遍历一遍
        //只不过这次 如果left > right left right置0
        //这样会把所有right > left时也可以出现的有效括号漏掉
        //不过没关系 在上面的遍历中弥补了
        if (s == "")
        {
            return 0;
        }
        int left = 0;
        int right = 0;
        int size = s.size();
        int ret = 0;
        for (int i = 0; i < size; i++)
        {
            if (s[i] == '(')
            {
                left++;
            }
            else
            {
                right++;
            }
            if (right > left)
            {
                //右括号多的情况直接扔了
                left = 0;
                right = 0;
            }
            else if (left == right)
            {
                //想等时说明出现了有效括号
                if (2 * left > ret)
                {
                    ret = 2 * left;
                }
            }
        }
        //上面那种情况舍弃了left > right也可能出现的有效括号
        //从后往前走的时候就会补上这种情况
        left = 0;
        right = 0;
        for (int i = size - 1; i >= 0; i--)
        {
            if (s[i] == '(')
            {
                left++;
            }
            else
            {
                right++;
            }
            if (left > right)
            {
                //左括号多的情况直接扔了
                left = 0;
                right = 0;
            }
            else if (left == right)
            {
                //想等时说明出现了有效括号
                if (2 * left > ret)
                {
                    ret = 2 * left;
                }
            }
        }
        return ret;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值